use crate::stub::model::{
BaseType, GenericClassSignature, GenericMethodSignature, TypeArgument, TypeParameterStub,
TypeSignature,
};
use crate::{ClasspathError, ClasspathResult};
pub fn parse_class_signature(input: &str) -> ClasspathResult<GenericClassSignature> {
let mut parser = SignatureParser::new(input);
let result = parser.parse_class_signature()?;
parser.expect_end()?;
Ok(result)
}
pub fn parse_method_signature(input: &str) -> ClasspathResult<GenericMethodSignature> {
let mut parser = SignatureParser::new(input);
let result = parser.parse_method_signature()?;
parser.expect_end()?;
Ok(result)
}
pub fn parse_field_signature(input: &str) -> ClasspathResult<TypeSignature> {
let mut parser = SignatureParser::new(input);
let result = parser.parse_field_type_signature()?;
parser.expect_end()?;
Ok(result)
}
struct SignatureParser<'a> {
input: &'a [u8],
pos: usize,
}
impl<'a> SignatureParser<'a> {
fn new(input: &'a str) -> Self {
Self {
input: input.as_bytes(),
pos: 0,
}
}
fn peek(&self) -> Option<u8> {
self.input.get(self.pos).copied()
}
fn advance(&mut self) {
self.pos += 1;
}
fn expect(&mut self, expected: u8) -> ClasspathResult<()> {
match self.peek() {
Some(b) if b == expected => {
self.advance();
Ok(())
}
Some(b) => Err(self.error(format!(
"expected '{}' but found '{}' at position {}",
expected as char, b as char, self.pos
))),
None => Err(self.error(format!(
"expected '{}' but reached end of input at position {}",
expected as char, self.pos
))),
}
}
fn expect_end(&self) -> ClasspathResult<()> {
if self.pos == self.input.len() {
Ok(())
} else {
let remaining = &self.input[self.pos..];
let remaining_str = std::str::from_utf8(remaining).unwrap_or("<invalid utf8>");
Err(self.error(format!(
"unexpected trailing input at position {}: {:?}",
self.pos, remaining_str
)))
}
}
fn read_identifier(&mut self) -> ClasspathResult<String> {
let start = self.pos;
while self.pos < self.input.len() && !is_delimiter(self.input[self.pos]) {
self.pos += 1;
}
if self.pos == start {
return Err(self.error(format!(
"expected identifier at position {} but found '{}'",
self.pos,
self.peek()
.map_or_else(|| "end of input".to_owned(), |b| (b as char).to_string())
)));
}
let ident = std::str::from_utf8(&self.input[start..self.pos])
.map_err(|e| self.error(format!("invalid UTF-8 in identifier: {e}")))?;
Ok(ident.to_owned())
}
fn error(&self, reason: String) -> ClasspathError {
let input_str = std::str::from_utf8(self.input).unwrap_or("<invalid utf8>");
ClasspathError::BytecodeParseError {
class_name: format!("<signature:{input_str}>"),
reason,
}
}
fn parse_class_signature(&mut self) -> ClasspathResult<GenericClassSignature> {
let type_parameters = if self.peek() == Some(b'<') {
self.parse_formal_type_parameters()?
} else {
Vec::new()
};
let superclass = self.parse_class_type_signature()?;
let mut interfaces = Vec::new();
while self.peek() == Some(b'L') {
interfaces.push(self.parse_class_type_signature()?);
}
Ok(GenericClassSignature {
type_parameters,
superclass,
interfaces,
})
}
fn parse_method_signature(&mut self) -> ClasspathResult<GenericMethodSignature> {
let type_parameters = if self.peek() == Some(b'<') {
self.parse_formal_type_parameters()?
} else {
Vec::new()
};
self.expect(b'(')?;
let mut parameter_types = Vec::new();
while self.peek() != Some(b')') {
parameter_types.push(self.parse_type_signature()?);
}
self.expect(b')')?;
let return_type = self.parse_return_type()?;
let mut exception_types = Vec::new();
while self.peek() == Some(b'^') {
self.advance(); exception_types.push(self.parse_throws_target()?);
}
Ok(GenericMethodSignature {
type_parameters,
parameter_types,
return_type,
exception_types,
})
}
fn parse_formal_type_parameters(&mut self) -> ClasspathResult<Vec<TypeParameterStub>> {
self.expect(b'<')?;
let mut params = Vec::new();
while self.peek() != Some(b'>') {
params.push(self.parse_formal_type_parameter()?);
}
self.expect(b'>')?;
if params.is_empty() {
return Err(self.error("formal type parameter list must not be empty".to_owned()));
}
Ok(params)
}
fn parse_formal_type_parameter(&mut self) -> ClasspathResult<TypeParameterStub> {
let name = self.read_identifier()?;
self.expect(b':')?;
let class_bound = if is_field_type_start(self.peek()) {
Some(self.parse_field_type_signature()?)
} else {
None
};
let mut interface_bounds = Vec::new();
while self.peek() == Some(b':') {
self.advance(); interface_bounds.push(self.parse_field_type_signature()?);
}
Ok(TypeParameterStub {
name,
class_bound,
interface_bounds,
})
}
fn parse_field_type_signature(&mut self) -> ClasspathResult<TypeSignature> {
match self.peek() {
Some(b'L') => self.parse_class_type_signature(),
Some(b'[') => self.parse_array_type_signature(),
Some(b'T') => self.parse_type_variable_signature(),
Some(b) => Err(self.error(format!(
"expected field type signature (L, [, or T) but found '{}' at position {}",
b as char, self.pos
))),
None => {
Err(self.error("expected field type signature but reached end of input".to_owned()))
}
}
}
fn parse_class_type_signature(&mut self) -> ClasspathResult<TypeSignature> {
self.expect(b'L')?;
let mut segments: Vec<String> = Vec::new();
loop {
let ident = self.read_identifier()?;
segments.push(ident);
if self.peek() == Some(b'/') {
self.advance(); } else {
break;
}
}
let fqn = segments.join(".");
let mut type_arguments = if self.peek() == Some(b'<') {
self.parse_type_arguments()?
} else {
Vec::new()
};
let mut full_fqn = fqn;
while self.peek() == Some(b'.') {
self.advance(); let inner_name = self.read_identifier()?;
full_fqn = format!("{full_fqn}${inner_name}");
type_arguments = if self.peek() == Some(b'<') {
self.parse_type_arguments()?
} else {
Vec::new()
};
}
self.expect(b';')?;
Ok(TypeSignature::Class {
fqn: full_fqn,
type_arguments,
})
}
fn parse_type_arguments(&mut self) -> ClasspathResult<Vec<TypeArgument>> {
self.expect(b'<')?;
let mut args = Vec::new();
while self.peek() != Some(b'>') {
args.push(self.parse_type_argument()?);
}
self.expect(b'>')?;
if args.is_empty() {
return Err(self.error("type argument list must not be empty".to_owned()));
}
Ok(args)
}
fn parse_type_argument(&mut self) -> ClasspathResult<TypeArgument> {
match self.peek() {
Some(b'*') => {
self.advance();
Ok(TypeArgument::Unbounded)
}
Some(b'+') => {
self.advance();
let sig = self.parse_field_type_signature()?;
Ok(TypeArgument::Extends(sig))
}
Some(b'-') => {
self.advance();
let sig = self.parse_field_type_signature()?;
Ok(TypeArgument::Super(sig))
}
_ => {
let sig = self.parse_field_type_signature()?;
Ok(TypeArgument::Type(sig))
}
}
}
fn parse_type_variable_signature(&mut self) -> ClasspathResult<TypeSignature> {
self.expect(b'T')?;
let name = self.read_identifier()?;
self.expect(b';')?;
Ok(TypeSignature::TypeVariable(name))
}
fn parse_array_type_signature(&mut self) -> ClasspathResult<TypeSignature> {
self.expect(b'[')?;
let element = self.parse_type_signature()?;
Ok(TypeSignature::Array(Box::new(element)))
}
fn parse_type_signature(&mut self) -> ClasspathResult<TypeSignature> {
match self.peek() {
Some(b'L' | b'[' | b'T') => self.parse_field_type_signature(),
Some(b) if is_base_type(b) => {
self.advance();
Ok(TypeSignature::Base(byte_to_base_type(b)?))
}
Some(b) => Err(self.error(format!(
"expected type signature but found '{}' at position {}",
b as char, self.pos
))),
None => Err(self.error("expected type signature but reached end of input".to_owned())),
}
}
fn parse_return_type(&mut self) -> ClasspathResult<TypeSignature> {
if self.peek() == Some(b'V') {
self.advance();
Ok(TypeSignature::Base(BaseType::Void))
} else {
self.parse_type_signature()
}
}
fn parse_throws_target(&mut self) -> ClasspathResult<TypeSignature> {
match self.peek() {
Some(b'L') => self.parse_class_type_signature(),
Some(b'T') => self.parse_type_variable_signature(),
Some(b) => Err(self.error(format!(
"expected class or type variable in throws signature but found '{}' at position {}",
b as char, self.pos
))),
None => Err(self.error("expected throws target but reached end of input".to_owned())),
}
}
}
fn is_delimiter(b: u8) -> bool {
matches!(
b,
b':' | b';' | b'<' | b'>' | b'.' | b'/' | b'(' | b')' | b'[' | b'^'
)
}
fn is_field_type_start(b: Option<u8>) -> bool {
matches!(b, Some(b'L' | b'[' | b'T'))
}
fn is_base_type(b: u8) -> bool {
matches!(b, b'B' | b'C' | b'D' | b'F' | b'I' | b'J' | b'S' | b'Z')
}
fn byte_to_base_type(b: u8) -> ClasspathResult<BaseType> {
match b {
b'B' => Ok(BaseType::Byte),
b'C' => Ok(BaseType::Char),
b'D' => Ok(BaseType::Double),
b'F' => Ok(BaseType::Float),
b'I' => Ok(BaseType::Int),
b'J' => Ok(BaseType::Long),
b'S' => Ok(BaseType::Short),
b'Z' => Ok(BaseType::Boolean),
_ => Err(ClasspathError::BytecodeParseError {
class_name: "<signature>".to_owned(),
reason: format!("unknown base type descriptor: '{}'", b as char),
}),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hashmap_class_signature() {
let input = "<K:Ljava/lang/Object;V:Ljava/lang/Object;>Ljava/util/AbstractMap<TK;TV;>;Ljava/util/Map<TK;TV;>;";
let sig = parse_class_signature(input).unwrap();
assert_eq!(sig.type_parameters.len(), 2);
let k = &sig.type_parameters[0];
assert_eq!(k.name, "K");
match &k.class_bound {
Some(TypeSignature::Class {
fqn,
type_arguments,
}) => {
assert_eq!(fqn, "java.lang.Object");
assert!(type_arguments.is_empty());
}
other => panic!("expected Class bound, got {other:?}"),
}
assert!(k.interface_bounds.is_empty());
let v = &sig.type_parameters[1];
assert_eq!(v.name, "V");
match &v.class_bound {
Some(TypeSignature::Class { fqn, .. }) => {
assert_eq!(fqn, "java.lang.Object");
}
other => panic!("expected Class bound, got {other:?}"),
}
match &sig.superclass {
TypeSignature::Class {
fqn,
type_arguments,
} => {
assert_eq!(fqn, "java.util.AbstractMap");
assert_eq!(type_arguments.len(), 2);
match &type_arguments[0] {
TypeArgument::Type(TypeSignature::TypeVariable(name)) => {
assert_eq!(name, "K");
}
other => panic!("expected TypeVariable K, got {other:?}"),
}
}
other => panic!("expected Class superclass, got {other:?}"),
}
assert_eq!(sig.interfaces.len(), 1);
match &sig.interfaces[0] {
TypeSignature::Class {
fqn,
type_arguments,
} => {
assert_eq!(fqn, "java.util.Map");
assert_eq!(type_arguments.len(), 2);
}
other => panic!("expected Class interface, got {other:?}"),
}
}
#[test]
fn test_wildcard_extends() {
let input = "Ljava/util/List<+Ljava/lang/Number;>;";
let sig = parse_field_signature(input).unwrap();
match sig {
TypeSignature::Class {
fqn,
type_arguments,
} => {
assert_eq!(fqn, "java.util.List");
assert_eq!(type_arguments.len(), 1);
match &type_arguments[0] {
TypeArgument::Extends(TypeSignature::Class { fqn, .. }) => {
assert_eq!(fqn, "java.lang.Number");
}
other => panic!("expected Extends(Number), got {other:?}"),
}
}
other => panic!("expected Class, got {other:?}"),
}
}
#[test]
fn test_nested_generics() {
let input = "Ljava/util/Map<Ljava/lang/String;Ljava/util/List<-Ljava/lang/Integer;>;>;";
let sig = parse_field_signature(input).unwrap();
match sig {
TypeSignature::Class {
fqn,
type_arguments,
} => {
assert_eq!(fqn, "java.util.Map");
assert_eq!(type_arguments.len(), 2);
match &type_arguments[0] {
TypeArgument::Type(TypeSignature::Class { fqn, .. }) => {
assert_eq!(fqn, "java.lang.String");
}
other => panic!("expected String, got {other:?}"),
}
match &type_arguments[1] {
TypeArgument::Type(TypeSignature::Class {
fqn,
type_arguments: inner_args,
}) => {
assert_eq!(fqn, "java.util.List");
assert_eq!(inner_args.len(), 1);
match &inner_args[0] {
TypeArgument::Super(TypeSignature::Class { fqn, .. }) => {
assert_eq!(fqn, "java.lang.Integer");
}
other => panic!("expected Super(Integer), got {other:?}"),
}
}
other => panic!("expected List<? super Integer>, got {other:?}"),
}
}
other => panic!("expected Class, got {other:?}"),
}
}
#[test]
fn test_bounded_type_parameter_self_reference() {
let input = "<T:Ljava/lang/Comparable<TT;>;>Ljava/lang/Object;";
let sig = parse_class_signature(input).unwrap();
assert_eq!(sig.type_parameters.len(), 1);
let t = &sig.type_parameters[0];
assert_eq!(t.name, "T");
match &t.class_bound {
Some(TypeSignature::Class {
fqn,
type_arguments,
}) => {
assert_eq!(fqn, "java.lang.Comparable");
assert_eq!(type_arguments.len(), 1);
match &type_arguments[0] {
TypeArgument::Type(TypeSignature::TypeVariable(name)) => {
assert_eq!(name, "T");
}
other => panic!("expected TypeVariable(T), got {other:?}"),
}
}
other => panic!("expected Comparable<T> bound, got {other:?}"),
}
}
#[test]
fn test_method_signature_with_type_params() {
let input = "<T:Ljava/lang/Object;>(TT;Ljava/util/List<TT;>;)TT;";
let sig = parse_method_signature(input).unwrap();
assert_eq!(sig.type_parameters.len(), 1);
assert_eq!(sig.type_parameters[0].name, "T");
assert_eq!(sig.parameter_types.len(), 2);
match &sig.parameter_types[0] {
TypeSignature::TypeVariable(name) => assert_eq!(name, "T"),
other => panic!("expected TypeVariable(T), got {other:?}"),
}
match &sig.parameter_types[1] {
TypeSignature::Class {
fqn,
type_arguments,
} => {
assert_eq!(fqn, "java.util.List");
assert_eq!(type_arguments.len(), 1);
}
other => panic!("expected List<T>, got {other:?}"),
}
match &sig.return_type {
TypeSignature::TypeVariable(name) => assert_eq!(name, "T"),
other => panic!("expected TypeVariable(T) return, got {other:?}"),
}
assert!(sig.exception_types.is_empty());
}
#[test]
fn test_array_type() {
let input = "[Ljava/lang/String;";
let sig = parse_field_signature(input).unwrap();
match sig {
TypeSignature::Array(inner) => match *inner {
TypeSignature::Class { ref fqn, .. } => {
assert_eq!(fqn, "java.lang.String");
}
other => panic!("expected Class inside Array, got {other:?}"),
},
other => panic!("expected Array, got {other:?}"),
}
}
#[test]
fn test_nested_array_type() {
let input = "([[I)V";
let sig = parse_method_signature(input).unwrap();
assert_eq!(sig.parameter_types.len(), 1);
match &sig.parameter_types[0] {
TypeSignature::Array(inner) => match inner.as_ref() {
TypeSignature::Array(inner2) => match inner2.as_ref() {
TypeSignature::Base(BaseType::Int) => {}
other => panic!("expected Base(Int), got {other:?}"),
},
other => panic!("expected Array inside Array, got {other:?}"),
},
other => panic!("expected Array, got {other:?}"),
}
}
#[test]
fn test_multiple_interface_bounds() {
let input = "<T:Ljava/lang/Object;:Ljava/io/Serializable;:Ljava/lang/Comparable<TT;>;>Ljava/lang/Object;";
let sig = parse_class_signature(input).unwrap();
assert_eq!(sig.type_parameters.len(), 1);
let t = &sig.type_parameters[0];
assert_eq!(t.name, "T");
match &t.class_bound {
Some(TypeSignature::Class { fqn, .. }) => {
assert_eq!(fqn, "java.lang.Object");
}
other => panic!("expected Object class bound, got {other:?}"),
}
assert_eq!(t.interface_bounds.len(), 2);
match &t.interface_bounds[0] {
TypeSignature::Class { fqn, .. } => {
assert_eq!(fqn, "java.io.Serializable");
}
other => panic!("expected Serializable, got {other:?}"),
}
match &t.interface_bounds[1] {
TypeSignature::Class {
fqn,
type_arguments,
} => {
assert_eq!(fqn, "java.lang.Comparable");
assert_eq!(type_arguments.len(), 1);
}
other => panic!("expected Comparable<T>, got {other:?}"),
}
}
#[test]
fn test_inner_class_signature() {
let input = "Ljava/util/Map.Entry<TK;TV;>;";
let sig = parse_field_signature(input).unwrap();
match sig {
TypeSignature::Class {
fqn,
type_arguments,
} => {
assert_eq!(fqn, "java.util.Map$Entry");
assert_eq!(type_arguments.len(), 2);
match &type_arguments[0] {
TypeArgument::Type(TypeSignature::TypeVariable(name)) => {
assert_eq!(name, "K");
}
other => panic!("expected TypeVariable(K), got {other:?}"),
}
match &type_arguments[1] {
TypeArgument::Type(TypeSignature::TypeVariable(name)) => {
assert_eq!(name, "V");
}
other => panic!("expected TypeVariable(V), got {other:?}"),
}
}
other => panic!("expected Class, got {other:?}"),
}
}
#[test]
fn test_nested_inner_class() {
let input = "Ljava/util/Outer.Middle.Inner;";
let sig = parse_field_signature(input).unwrap();
match sig {
TypeSignature::Class {
fqn,
type_arguments,
} => {
assert_eq!(fqn, "java.util.Outer$Middle$Inner");
assert!(type_arguments.is_empty());
}
other => panic!("expected Class, got {other:?}"),
}
}
#[test]
fn test_method_with_throws() {
let input = "<T:Ljava/lang/Exception;>(TT;)V^TT;^Ljava/io/IOException;";
let sig = parse_method_signature(input).unwrap();
assert_eq!(sig.type_parameters.len(), 1);
assert_eq!(sig.type_parameters[0].name, "T");
assert_eq!(sig.parameter_types.len(), 1);
match &sig.parameter_types[0] {
TypeSignature::TypeVariable(name) => assert_eq!(name, "T"),
other => panic!("expected TypeVariable(T), got {other:?}"),
}
match &sig.return_type {
TypeSignature::Base(BaseType::Void) => {}
other => panic!("expected Void return, got {other:?}"),
}
assert_eq!(sig.exception_types.len(), 2);
match &sig.exception_types[0] {
TypeSignature::TypeVariable(name) => assert_eq!(name, "T"),
other => panic!("expected TypeVariable(T) exception, got {other:?}"),
}
match &sig.exception_types[1] {
TypeSignature::Class { fqn, .. } => {
assert_eq!(fqn, "java.io.IOException");
}
other => panic!("expected IOException, got {other:?}"),
}
}
#[test]
fn test_malformed_empty() {
assert!(parse_class_signature("").is_err());
}
#[test]
fn test_malformed_missing_semicolon() {
assert!(parse_field_signature("Ljava/lang/Object").is_err());
}
#[test]
fn test_malformed_unclosed_type_args() {
assert!(parse_field_signature("Ljava/util/List<Ljava/lang/String;;").is_err());
}
#[test]
fn test_malformed_missing_class_bound_colon() {
assert!(parse_class_signature("<T>Ljava/lang/Object;").is_err());
}
#[test]
fn test_malformed_trailing_input() {
assert!(parse_field_signature("Ljava/lang/Object;GARBAGE").is_err());
}
#[test]
fn test_malformed_method_missing_paren() {
assert!(parse_method_signature("V").is_err());
}
#[test]
fn test_unbounded_wildcard() {
let input = "Ljava/util/List<*>;";
let sig = parse_field_signature(input).unwrap();
match sig {
TypeSignature::Class {
fqn,
type_arguments,
} => {
assert_eq!(fqn, "java.util.List");
assert_eq!(type_arguments.len(), 1);
assert!(matches!(type_arguments[0], TypeArgument::Unbounded));
}
other => panic!("expected Class, got {other:?}"),
}
}
#[test]
fn test_method_void_return() {
let input = "()V";
let sig = parse_method_signature(input).unwrap();
assert!(sig.type_parameters.is_empty());
assert!(sig.parameter_types.is_empty());
assert!(matches!(
sig.return_type,
TypeSignature::Base(BaseType::Void)
));
assert!(sig.exception_types.is_empty());
}
#[test]
fn test_method_primitive_params() {
let input = "(IDZ)J";
let sig = parse_method_signature(input).unwrap();
assert_eq!(sig.parameter_types.len(), 3);
assert!(matches!(
sig.parameter_types[0],
TypeSignature::Base(BaseType::Int)
));
assert!(matches!(
sig.parameter_types[1],
TypeSignature::Base(BaseType::Double)
));
assert!(matches!(
sig.parameter_types[2],
TypeSignature::Base(BaseType::Boolean)
));
assert!(matches!(
sig.return_type,
TypeSignature::Base(BaseType::Long)
));
}
#[test]
fn test_empty_class_bound_with_interface_bound() {
let input = "<T::Ljava/io/Serializable;>Ljava/lang/Object;";
let sig = parse_class_signature(input).unwrap();
assert_eq!(sig.type_parameters.len(), 1);
let t = &sig.type_parameters[0];
assert_eq!(t.name, "T");
assert!(t.class_bound.is_none());
assert_eq!(t.interface_bounds.len(), 1);
match &t.interface_bounds[0] {
TypeSignature::Class { fqn, .. } => {
assert_eq!(fqn, "java.io.Serializable");
}
other => panic!("expected Serializable, got {other:?}"),
}
}
#[test]
fn test_type_variable_field_signature() {
let input = "TT;";
let sig = parse_field_signature(input).unwrap();
match sig {
TypeSignature::TypeVariable(name) => assert_eq!(name, "T"),
other => panic!("expected TypeVariable, got {other:?}"),
}
}
#[test]
fn test_complex_real_world_class_signature() {
let input =
"<K:Ljava/lang/Object;V:Ljava/lang/Object;>Ljava/lang/Object;Ljava/util/Map<TK;TV;>;";
let sig = parse_class_signature(input).unwrap();
assert_eq!(sig.type_parameters.len(), 2);
match &sig.superclass {
TypeSignature::Class {
fqn,
type_arguments,
} => {
assert_eq!(fqn, "java.lang.Object");
assert!(type_arguments.is_empty());
}
other => panic!("expected Object superclass, got {other:?}"),
}
assert_eq!(sig.interfaces.len(), 1);
}
#[test]
fn test_array_of_generic_type() {
let input = "([Ljava/util/List<Ljava/lang/String;>;)V";
let sig = parse_method_signature(input).unwrap();
assert_eq!(sig.parameter_types.len(), 1);
match &sig.parameter_types[0] {
TypeSignature::Array(inner) => match inner.as_ref() {
TypeSignature::Class {
fqn,
type_arguments,
} => {
assert_eq!(fqn, "java.util.List");
assert_eq!(type_arguments.len(), 1);
}
other => panic!("expected List<String> inside array, got {other:?}"),
},
other => panic!("expected Array, got {other:?}"),
}
}
#[test]
fn test_wildcard_super_bound() {
let input = "Ljava/lang/Comparable<-TT;>;";
let sig = parse_field_signature(input).unwrap();
match sig {
TypeSignature::Class {
fqn,
type_arguments,
} => {
assert_eq!(fqn, "java.lang.Comparable");
assert_eq!(type_arguments.len(), 1);
match &type_arguments[0] {
TypeArgument::Super(TypeSignature::TypeVariable(name)) => {
assert_eq!(name, "T");
}
other => panic!("expected Super(T), got {other:?}"),
}
}
other => panic!("expected Class, got {other:?}"),
}
}
#[test]
fn test_inner_class_with_outer_type_args() {
let input = "Ljava/util/Outer<Ljava/lang/String;>.Inner;";
let sig = parse_field_signature(input).unwrap();
match sig {
TypeSignature::Class {
fqn,
type_arguments,
} => {
assert_eq!(fqn, "java.util.Outer$Inner");
assert!(type_arguments.is_empty());
}
other => panic!("expected Class, got {other:?}"),
}
}
#[test]
fn test_multiple_type_params_method() {
let input = "<K:Ljava/lang/Object;V:Ljava/lang/Object;>(TK;TV;)V";
let sig = parse_method_signature(input).unwrap();
assert_eq!(sig.type_parameters.len(), 2);
assert_eq!(sig.type_parameters[0].name, "K");
assert_eq!(sig.type_parameters[1].name, "V");
assert_eq!(sig.parameter_types.len(), 2);
assert!(matches!(
sig.return_type,
TypeSignature::Base(BaseType::Void)
));
}
}