Skip to main content

lvm_common/
jtype.rs

1use crate::error::TypeDescriptorErr;
2use core::fmt;
3use num_enum::TryFromPrimitive;
4use std::fmt::{Display, Formatter};
5use std::iter::Peekable;
6
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub enum TypeArg {
9    Any,                    // '*'
10    Extends(Box<JavaType>), // '+'
11    Super(Box<JavaType>),   // '-'
12    Exact(Box<JavaType>),   // no prefix
13}
14
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct ClassSignatureSegment {
17    pub name: String,
18    pub args: Vec<TypeArg>,
19}
20
21#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct ClassSignature {
23    pub first: ClassSignatureSegment,
24    pub suffix: Vec<ClassSignatureSegment>,
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub enum PrimitiveType {
29    Byte,
30    Char,
31    Double,
32    Float,
33    Int,
34    Long,
35    Short,
36    Boolean,
37}
38
39impl PrimitiveType {
40    pub fn values() -> &'static [PrimitiveType] {
41        static PRIMITIVE_TYPES: [PrimitiveType; 8] = [
42            PrimitiveType::Byte,
43            PrimitiveType::Char,
44            PrimitiveType::Double,
45            PrimitiveType::Float,
46            PrimitiveType::Int,
47            PrimitiveType::Long,
48            PrimitiveType::Short,
49            PrimitiveType::Boolean,
50        ];
51        &PRIMITIVE_TYPES
52    }
53}
54
55impl Display for PrimitiveType {
56    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
57        match self {
58            PrimitiveType::Byte => write!(f, "byte"),
59            PrimitiveType::Char => write!(f, "char"),
60            PrimitiveType::Double => write!(f, "double"),
61            PrimitiveType::Float => write!(f, "float"),
62            PrimitiveType::Int => write!(f, "int"),
63            PrimitiveType::Long => write!(f, "long"),
64            PrimitiveType::Short => write!(f, "short"),
65            PrimitiveType::Boolean => write!(f, "boolean"),
66        }
67    }
68}
69
70impl TryFrom<char> for PrimitiveType {
71    type Error = (); // todo
72
73    fn try_from(value: char) -> Result<Self, Self::Error> {
74        match value {
75            'B' => Ok(PrimitiveType::Byte),
76            'C' => Ok(PrimitiveType::Char),
77            'D' => Ok(PrimitiveType::Double),
78            'F' => Ok(PrimitiveType::Float),
79            'I' => Ok(PrimitiveType::Int),
80            'J' => Ok(PrimitiveType::Long),
81            'S' => Ok(PrimitiveType::Short),
82            'Z' => Ok(PrimitiveType::Boolean),
83            _ => Err(()),
84        }
85    }
86}
87
88#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive)]
89#[repr(u8)]
90pub enum AllocationType {
91    Boolean = 0,
92    Byte = 1,
93    Char = 2,
94    Short = 3,
95    Int = 4,
96    Long = 5,
97    Float = 6,
98    Double = 7,
99    Reference = 8,
100}
101
102impl AllocationType {
103    pub fn byte_size(&self) -> usize {
104        match self {
105            AllocationType::Byte | AllocationType::Boolean => 1,
106            AllocationType::Char | AllocationType::Short => 2,
107            AllocationType::Int | AllocationType::Float => 4,
108            AllocationType::Long | AllocationType::Double => 8,
109            AllocationType::Reference => 8, // TODO: I guess it is 4 bytes on hotspot, mine is HeapRef usize
110        }
111    }
112}
113
114/// Represents any actual Java type (cannot be void)
115/// Used for field descriptors and method parameters
116#[derive(Debug, Clone, PartialEq, Eq)]
117pub enum JavaType {
118    Primitive(PrimitiveType),
119    Instance(String), // TODO: should be interned?
120    GenericInstance(ClassSignature),
121    TypeVar(String),
122    Array(Box<JavaType>),
123}
124
125/// Represents a method return type (can be void or any JavaType)
126#[derive(Debug, Clone, PartialEq, Eq)]
127pub enum ReturnType {
128    Void,
129    Type(JavaType),
130}
131
132impl TryFrom<&str> for JavaType {
133    type Error = TypeDescriptorErr;
134
135    fn try_from(value: &str) -> Result<Self, Self::Error> {
136        JavaType::try_recursive(&mut value.chars().peekable())
137    }
138}
139
140impl JavaType {
141    pub fn as_allocation_type(&self) -> AllocationType {
142        match self {
143            JavaType::Primitive(prim) => match prim {
144                PrimitiveType::Byte => AllocationType::Byte,
145                PrimitiveType::Char => AllocationType::Char,
146                PrimitiveType::Double => AllocationType::Double,
147                PrimitiveType::Float => AllocationType::Float,
148                PrimitiveType::Int => AllocationType::Int,
149                PrimitiveType::Long => AllocationType::Long,
150                PrimitiveType::Short => AllocationType::Short,
151                PrimitiveType::Boolean => AllocationType::Boolean,
152            },
153            JavaType::Instance(_) | JavaType::Array(_) => AllocationType::Reference,
154            _ => panic!("Cannot get allocation type for non-primitive, non-instance JavaType"),
155        }
156    }
157
158    //TODO: cache? precompute?
159    pub fn get_array_dimension(&self) -> usize {
160        match self {
161            JavaType::Array(elem) => 1 + elem.get_array_dimension(),
162            _ => 0,
163        }
164    }
165
166    //TODO: bad, can be optimized. for example when parsing we can store the interned descriptor
167    pub fn as_descriptor(&self) -> String {
168        match self {
169            JavaType::Primitive(prim) => match prim {
170                PrimitiveType::Byte => "B".to_string(),
171                PrimitiveType::Char => "C".to_string(),
172                PrimitiveType::Double => "D".to_string(),
173                PrimitiveType::Float => "F".to_string(),
174                PrimitiveType::Int => "I".to_string(),
175                PrimitiveType::Long => "J".to_string(),
176                PrimitiveType::Short => "S".to_string(),
177                PrimitiveType::Boolean => "Z".to_string(),
178            },
179            JavaType::Instance(name) => format!("L{};", name),
180            JavaType::GenericInstance(sig) => {
181                let mut desc = format!("L{}", sig.first.name);
182                if !sig.first.args.is_empty() {
183                    desc.push('<');
184                    for arg in &sig.first.args {
185                        match arg {
186                            TypeArg::Any => desc.push('*'),
187                            TypeArg::Extends(t) => {
188                                desc.push('+');
189                                desc.push_str(&t.as_descriptor());
190                            }
191                            TypeArg::Super(t) => {
192                                desc.push('-');
193                                desc.push_str(&t.as_descriptor());
194                            }
195                            TypeArg::Exact(t) => {
196                                desc.push_str(&t.as_descriptor());
197                            }
198                        }
199                    }
200                    desc.push('>');
201                }
202                for seg in &sig.suffix {
203                    desc.push('.');
204                    desc.push_str(&seg.name);
205                    if !seg.args.is_empty() {
206                        desc.push('<');
207                        for arg in &seg.args {
208                            match arg {
209                                TypeArg::Any => desc.push('*'),
210                                TypeArg::Extends(t) => {
211                                    desc.push('+');
212                                    desc.push_str(&t.as_descriptor());
213                                }
214                                TypeArg::Super(t) => {
215                                    desc.push('-');
216                                    desc.push_str(&t.as_descriptor());
217                                }
218                                TypeArg::Exact(t) => {
219                                    desc.push_str(&t.as_descriptor());
220                                }
221                            }
222                        }
223                        desc.push('>');
224                    }
225                }
226                desc.push(';');
227                desc
228            }
229            JavaType::TypeVar(name) => format!("T{};", name),
230            JavaType::Array(elem) => format!("[{}", elem.as_descriptor()),
231        }
232    }
233
234    pub fn get_array_element_type(&self) -> Option<&JavaType> {
235        match self {
236            JavaType::Array(elem) => match elem.as_ref() {
237                JavaType::Primitive(_) | JavaType::Instance(_) | JavaType::Array(_) => Some(elem),
238                _ => None,
239            },
240            _ => None,
241        }
242    }
243
244    pub fn is_primitive_array(&self) -> bool {
245        match self {
246            JavaType::Array(elem) => matches!(**elem, JavaType::Primitive(_)),
247            _ => false,
248        }
249    }
250
251    /// Parse a JavaType from a character iterator
252    /// This cannot parse 'V' (void) - will return an error
253    pub fn try_recursive<I>(it: &mut Peekable<I>) -> Result<JavaType, TypeDescriptorErr>
254    where
255        I: Iterator<Item = char>,
256    {
257        let c = it.peek().ok_or(TypeDescriptorErr::UnexpectedEnd)?;
258
259        // Void is not allowed in JavaType
260        if *c == 'V' {
261            return Err(TypeDescriptorErr::InvalidType('V'));
262        }
263
264        // Try to parse primitive
265        if let Ok(prim) = PrimitiveType::try_from(*c) {
266            it.next();
267            return Ok(JavaType::Primitive(prim));
268        }
269
270        match c {
271            '[' => {
272                it.next();
273                let elem = JavaType::try_recursive(it)?;
274                Ok(JavaType::Array(Box::new(elem)))
275            }
276            'L' => {
277                it.next();
278                let mut name = String::new();
279                let mut has_type_args = false;
280                let mut has_dot = false;
281                let mut depth = 0;
282
283                loop {
284                    let ch = it.next().ok_or(TypeDescriptorErr::UnexpectedEnd)?;
285                    match ch {
286                        '<' => {
287                            has_type_args = true;
288                            depth += 1;
289                            name.push(ch);
290                        }
291                        '>' => {
292                            depth -= 1;
293                            name.push(ch);
294                            if depth == 0 {
295                                // After closing type args, only '.' or ';' are valid
296                                match it.peek() {
297                                    Some('.') | Some(';') => {}
298                                    Some(&c) => return Err(TypeDescriptorErr::InvalidType(c)),
299                                    None => return Err(TypeDescriptorErr::UnexpectedEnd),
300                                }
301                            }
302                        }
303                        '.' => {
304                            has_dot = true;
305                            name.push(ch);
306                        }
307                        ';' => {
308                            if depth == 0 {
309                                break;
310                            }
311                            name.push(ch);
312                        }
313                        _ => {
314                            name.push(ch);
315                        }
316                    }
317                }
318
319                if has_type_args || has_dot {
320                    let sig = Self::parse_class_signature(&name)?;
321                    Ok(JavaType::GenericInstance(sig))
322                } else {
323                    Ok(JavaType::Instance(name))
324                }
325            }
326            'T' => {
327                it.next();
328                let mut name = String::new();
329                loop {
330                    let ch = it.next().ok_or(TypeDescriptorErr::UnexpectedEnd)?;
331                    if ch == ';' {
332                        break;
333                    }
334                    name.push(ch);
335                }
336                Ok(JavaType::TypeVar(name))
337            }
338            _ => Err(TypeDescriptorErr::InvalidType(*c)),
339        }
340    }
341
342    fn parse_class_signature(s: &str) -> Result<ClassSignature, TypeDescriptorErr> {
343        let mut it = s.chars().peekable();
344        let mut segments = Vec::new();
345
346        loop {
347            let mut seg_name = String::new();
348
349            // Read until '<' or '.' or end
350            while let Some(&ch) = it.peek() {
351                if ch == '<' || ch == '.' {
352                    break;
353                }
354                seg_name.push(ch);
355                it.next();
356            }
357
358            let mut args = Vec::new();
359            if it.peek() == Some(&'<') {
360                it.next(); // consume '<'
361                loop {
362                    match it.peek() {
363                        Some('>') => {
364                            it.next(); // consume '>'
365                            break;
366                        }
367                        Some('*') => {
368                            it.next();
369                            args.push(TypeArg::Any);
370                        }
371                        Some('+') => {
372                            it.next();
373                            let ty = JavaType::try_recursive(&mut it)?;
374                            args.push(TypeArg::Extends(Box::new(ty)));
375                        }
376                        Some('-') => {
377                            it.next();
378                            let ty = JavaType::try_recursive(&mut it)?;
379                            args.push(TypeArg::Super(Box::new(ty)));
380                        }
381                        Some(_) => {
382                            let ty = JavaType::try_recursive(&mut it)?;
383                            args.push(TypeArg::Exact(Box::new(ty)));
384                        }
385                        None => return Err(TypeDescriptorErr::UnexpectedEnd),
386                    }
387                }
388            }
389
390            segments.push(ClassSignatureSegment {
391                name: seg_name,
392                args,
393            });
394
395            if it.peek() == Some(&'.') {
396                it.next(); // consume '.'
397            } else {
398                break;
399            }
400        }
401
402        if segments.is_empty() {
403            return Err(TypeDescriptorErr::UnexpectedEnd);
404        }
405
406        let first = segments.remove(0);
407        Ok(ClassSignature {
408            first,
409            suffix: segments,
410        })
411    }
412}
413
414impl ReturnType {
415    /// Parse a ReturnType from a character iterator
416    /// This can parse both 'V' (void) and any JavaType
417    pub fn try_recursive<I>(it: &mut Peekable<I>) -> Result<ReturnType, TypeDescriptorErr>
418    where
419        I: Iterator<Item = char>,
420    {
421        let c = it.peek().ok_or(TypeDescriptorErr::UnexpectedEnd)?;
422
423        if *c == 'V' {
424            it.next();
425            return Ok(ReturnType::Void);
426        }
427
428        let java_type = JavaType::try_recursive(it)?;
429        Ok(ReturnType::Type(java_type))
430    }
431}
432
433impl Display for JavaType {
434    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
435        match self {
436            JavaType::Primitive(prim) => {
437                let name = match prim {
438                    PrimitiveType::Byte => "byte",
439                    PrimitiveType::Char => "char",
440                    PrimitiveType::Double => "double",
441                    PrimitiveType::Float => "float",
442                    PrimitiveType::Int => "int",
443                    PrimitiveType::Long => "long",
444                    PrimitiveType::Short => "short",
445                    PrimitiveType::Boolean => "boolean",
446                };
447                write!(f, "{}", name)
448            }
449
450            JavaType::Instance(name) => write!(f, "{}", name.replace('/', ".")),
451
452            JavaType::GenericInstance(sig) => {
453                // first segment with args
454                write!(f, "{}", sig.first.name.replace('/', "."))?;
455                if !sig.first.args.is_empty() {
456                    write!(f, "<")?;
457                    for (i, a) in sig.first.args.iter().enumerate() {
458                        if i > 0 {
459                            write!(f, ", ")?;
460                        }
461                        write!(f, "{a}")?;
462                    }
463                    write!(f, ">")?;
464                }
465                // suffix segments with args
466                for seg in &sig.suffix {
467                    write!(f, ".{}", seg.name)?;
468                    if !seg.args.is_empty() {
469                        write!(f, "<")?;
470                        for (i, a) in seg.args.iter().enumerate() {
471                            if i > 0 {
472                                write!(f, ", ")?;
473                            }
474                            write!(f, "{a}")?;
475                        }
476                        write!(f, ">")?;
477                    }
478                }
479                Ok(())
480            }
481
482            JavaType::TypeVar(name) => write!(f, "{}", name),
483
484            JavaType::Array(elem) => write!(f, "{}[]", elem),
485        }
486    }
487}
488
489impl Display for ReturnType {
490    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
491        match self {
492            ReturnType::Void => write!(f, "void"),
493            ReturnType::Type(java_type) => write!(f, "{}", java_type),
494        }
495    }
496}
497
498impl fmt::Display for TypeArg {
499    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
500        match self {
501            TypeArg::Any => write!(f, "?"),
502            TypeArg::Extends(t) => write!(f, "? extends {}", t),
503            TypeArg::Super(t) => write!(f, "? super {}", t),
504            TypeArg::Exact(t) => write!(f, "{t}"),
505        }
506    }
507}
508
509#[cfg(test)]
510mod tests {
511    use super::*;
512
513    fn parse_one_descriptor(s: &str) -> Result<ReturnType, TypeDescriptorErr> {
514        let mut it = s.chars().peekable();
515        ReturnType::try_recursive(&mut it)
516    }
517
518    fn parse_one_java(s: &str) -> Result<JavaType, TypeDescriptorErr> {
519        let mut it = s.chars().peekable();
520        JavaType::try_recursive(&mut it)
521    }
522
523    fn parse_one_return(s: &str) -> Result<ReturnType, TypeDescriptorErr> {
524        let mut it = s.chars().peekable();
525        ReturnType::try_recursive(&mut it)
526    }
527
528    fn parse_and_rest(s: &str) -> (Result<ReturnType, TypeDescriptorErr>, String) {
529        let mut it = s.chars().peekable();
530        let res = ReturnType::try_recursive(&mut it);
531        let rest: String = it.collect();
532        (res, rest)
533    }
534
535    #[test]
536    fn primitives_try_from_char() {
537        let cases = vec![
538            ('B', PrimitiveType::Byte),
539            ('C', PrimitiveType::Char),
540            ('D', PrimitiveType::Double),
541            ('F', PrimitiveType::Float),
542            ('I', PrimitiveType::Int),
543            ('J', PrimitiveType::Long),
544            ('S', PrimitiveType::Short),
545            ('Z', PrimitiveType::Boolean),
546        ];
547        for (ch, ty) in cases {
548            assert_eq!(PrimitiveType::try_from(ch), Ok(ty));
549        }
550        // invalid primitive
551        assert!(PrimitiveType::try_from('Q').is_err());
552        // 'V' (Void) is handled by the `Type` enum directly, it's not a `PrimitiveType`
553        assert!(PrimitiveType::try_from('V').is_err());
554    }
555
556    #[test]
557    fn parse_void_descriptor() {
558        assert_eq!(parse_one_descriptor("V").unwrap(), ReturnType::Void);
559    }
560
561    #[test]
562    fn parse_void_return() {
563        assert_eq!(parse_one_return("V").unwrap(), ReturnType::Void);
564    }
565
566    #[test]
567    fn parse_void_java_type_should_fail() {
568        assert!(parse_one_java("V").is_err());
569    }
570
571    #[test]
572    fn parse_instance_object() {
573        assert_eq!(
574            parse_one_descriptor("Ljava/lang/String;").unwrap(),
575            ReturnType::Type(JavaType::Instance("java/lang/String".to_string()))
576        );
577        assert_eq!(
578            parse_one_java("Ljava/lang/String;").unwrap(),
579            JavaType::Instance("java/lang/String".to_string())
580        );
581    }
582
583    #[test]
584    fn parse_array_of_primitive() {
585        assert_eq!(
586            parse_one_descriptor("[I").unwrap(),
587            ReturnType::Type(JavaType::Array(Box::new(JavaType::Primitive(
588                PrimitiveType::Int
589            ))))
590        );
591        assert_eq!(
592            parse_one_java("[I").unwrap(),
593            JavaType::Array(Box::new(JavaType::Primitive(PrimitiveType::Int)))
594        );
595    }
596
597    #[test]
598    fn parse_array_of_object() {
599        assert_eq!(
600            parse_one_descriptor("[Ljava/util/List;").unwrap(),
601            ReturnType::Type(JavaType::Array(Box::new(JavaType::Instance(
602                "java/util/List".to_string()
603            ))))
604        );
605        assert_eq!(
606            parse_one_java("[Ljava/util/List;").unwrap(),
607            JavaType::Array(Box::new(JavaType::Instance("java/util/List".to_string())))
608        );
609    }
610
611    #[test]
612    fn as_descriptor_tests() {
613        let jt = JavaType::Primitive(PrimitiveType::Int);
614        assert_eq!(jt.as_descriptor(), "I");
615
616        let jt = JavaType::Instance("java/lang/String".to_string());
617        assert_eq!(jt.as_descriptor(), "Ljava/lang/String;");
618
619        let jt = JavaType::Array(Box::new(JavaType::Primitive(PrimitiveType::Boolean)));
620        assert_eq!(jt.as_descriptor(), "[Z");
621
622        let jt = JavaType::Array(Box::new(JavaType::Instance("java/util/List".to_string())));
623        assert_eq!(jt.as_descriptor(), "[Ljava/util/List;");
624
625        let jt = JavaType::Array(Box::new(JavaType::Array(Box::new(JavaType::Primitive(
626            PrimitiveType::Int,
627        )))));
628        assert_eq!(jt.as_descriptor(), "[[I");
629
630        let jt = JavaType::Array(Box::new(JavaType::Array(Box::new(JavaType::Array(
631            Box::new(JavaType::Primitive(PrimitiveType::Int)),
632        )))));
633        assert_eq!(jt.as_descriptor(), "[[[I");
634    }
635
636    #[test]
637    fn parse_multi_dimensional_array() {
638        assert_eq!(
639            parse_one_descriptor("[[I").unwrap(),
640            ReturnType::Type(JavaType::Array(Box::new(JavaType::Array(Box::new(
641                JavaType::Primitive(PrimitiveType::Int)
642            )))))
643        );
644        assert_eq!(
645            parse_one_java("[[I").unwrap(),
646            JavaType::Array(Box::new(JavaType::Array(Box::new(JavaType::Primitive(
647                PrimitiveType::Int
648            )))))
649        );
650        assert_eq!(
651            parse_one_java("[[[I").unwrap(),
652            JavaType::Array(Box::new(JavaType::Array(Box::new(JavaType::Array(
653                Box::new(JavaType::Primitive(PrimitiveType::Int))
654            )))))
655        );
656        assert_eq!(
657            parse_one_descriptor("[[Ljava/lang/String;").unwrap(),
658            ReturnType::Type(JavaType::Array(Box::new(JavaType::Array(Box::new(
659                JavaType::Instance("java/lang/String".to_string())
660            )))))
661        );
662        assert_eq!(
663            parse_one_java("[[Ljava/lang/String;").unwrap(),
664            JavaType::Array(Box::new(JavaType::Array(Box::new(JavaType::Instance(
665                "java/lang/String".to_string()
666            )))))
667        );
668    }
669
670    #[test]
671    fn error_unexpected_end_after_l() {
672        // Missing ';' terminator
673        let err = parse_one_descriptor("Ljava/lang/String").unwrap_err();
674        assert!(matches!(err, TypeDescriptorErr::UnexpectedEnd));
675    }
676
677    #[test]
678    fn error_unexpected_end_after_array_prefix() {
679        let err = parse_one_descriptor("[").unwrap_err();
680        assert!(matches!(err, TypeDescriptorErr::UnexpectedEnd));
681    }
682
683    #[test]
684    fn error_invalid_type_tag() {
685        let err = parse_one_descriptor("Q").unwrap_err();
686        assert!(matches!(err, TypeDescriptorErr::InvalidType('Q')));
687    }
688
689    #[test]
690    fn consumes_exactly_one_type() {
691        let (res, rest) = parse_and_rest("I[Ljava/lang/String;");
692        assert_eq!(
693            res.unwrap(),
694            ReturnType::Type(JavaType::Primitive(PrimitiveType::Int))
695        );
696        assert_eq!(rest, "[Ljava/lang/String;".to_string()); // untouched remainder
697    }
698
699    // --- Generic Signature tests ---
700
701    #[test]
702    fn generic_first_segment_only() {
703        let s = "Ljava/util/List<+Ljava/lang/CharSequence;>;";
704        let t = parse_one_java(s).unwrap();
705        assert_eq!(
706            t,
707            JavaType::GenericInstance(ClassSignature {
708                first: ClassSignatureSegment {
709                    name: "java/util/List".into(),
710                    args: vec![TypeArg::Extends(Box::new(JavaType::Instance(
711                        "java/lang/CharSequence".into()
712                    )))],
713                },
714                suffix: vec![],
715            })
716        );
717    }
718
719    #[test]
720    fn generic_with_suffix_and_args() {
721        let s = "Ljava/util/Map<Ljava/lang/String;Ljava/lang/Integer;>.Entry<Ljava/lang/String;>;";
722        let t = parse_one_java(s).unwrap();
723        assert_eq!(
724            t,
725            JavaType::GenericInstance(ClassSignature {
726                first: ClassSignatureSegment {
727                    name: "java/util/Map".into(),
728                    args: vec![
729                        TypeArg::Exact(Box::new(JavaType::Instance("java/lang/String".into()))),
730                        TypeArg::Exact(Box::new(JavaType::Instance("java/lang/Integer".into()))),
731                    ],
732                },
733                suffix: vec![ClassSignatureSegment {
734                    name: "Entry".into(),
735                    args: vec![TypeArg::Exact(Box::new(JavaType::Instance(
736                        "java/lang/String".into()
737                    )))],
738                }],
739            })
740        );
741    }
742
743    #[test]
744    fn suffix_without_args() {
745        let s = "Lpkg/Outer.Inner;";
746        let t = parse_one_java(s).unwrap();
747        assert_eq!(
748            t,
749            JavaType::GenericInstance(ClassSignature {
750                first: ClassSignatureSegment {
751                    name: "pkg/Outer".into(),
752                    args: vec![]
753                },
754                suffix: vec![ClassSignatureSegment {
755                    name: "Inner".into(),
756                    args: vec![]
757                }],
758            })
759        );
760    }
761
762    #[test]
763    fn error_unexpected_end_in_type_args() {
764        // Missing '>'
765        let err = parse_one_java("Ljava/util/List<").unwrap_err();
766        assert!(matches!(err, TypeDescriptorErr::UnexpectedEnd));
767    }
768
769    #[test]
770    fn error_invalid_after_type_args() {
771        // After '<...>' must be '.' or ';'
772        let err = parse_one_java("Ljava/util/List<Ljava/lang/String;>X").unwrap_err();
773        assert!(matches!(err, TypeDescriptorErr::InvalidType('X')));
774    }
775}