1use log::warn;
11
12use crate::stub::model::ScalaSignatureStub;
13
14use super::signature::{
15 FLAG_ABSTRACT, FLAG_CASE, FLAG_INTERFACE, FLAG_PRIVATE, FLAG_PROTECTED, FLAG_SEALED,
16 FLAG_TRAIT, ScalaSignatureReader, TAG_EXT_MOD_CLASS_REF, TAG_EXT_REF, TAG_MODULE_SYM,
17};
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
25pub enum ScalaClassKind {
26 Class,
28 Trait,
30 Object,
32 PackageObject,
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
38pub enum ScalaVisibility {
39 Public,
41 Private,
43 Protected,
45}
46
47#[derive(Debug, Clone)]
53pub struct ScalaClassMetadata {
54 pub kind: ScalaClassKind,
56 pub visibility: ScalaVisibility,
58 pub is_case: bool,
60 pub is_sealed: bool,
62 pub is_abstract: bool,
64 pub superclass: Option<String>,
66 pub traits: Vec<String>,
68}
69
70pub fn decode_scala_signature(stub: &ScalaSignatureStub) -> Option<ScalaClassMetadata> {
84 if stub.major_version != 5 {
86 warn!(
87 "unsupported Scala signature major version {} (expected 5)",
88 stub.major_version
89 );
90 return None;
91 }
92
93 let reader = ScalaSignatureReader::parse(&stub.bytes)?;
94
95 let symbols = reader.class_and_module_symbols();
100 if symbols.is_empty() {
101 warn!("no CLASSsym or MODULEsym entries found in Scala signature");
102 return None;
103 }
104
105 let (primary_index, primary_entry) = find_primary_symbol(&reader, &symbols)?;
108
109 let info = reader.read_symbol_info(primary_entry)?;
110 let flags = info.flags;
111
112 let kind = determine_kind(primary_entry.tag, flags, &reader, info.name_index);
114
115 let visibility = determine_visibility(flags);
117
118 let (superclass, traits) = extract_hierarchy(&reader, primary_index);
120
121 Some(ScalaClassMetadata {
122 kind,
123 visibility,
124 is_case: flags & FLAG_CASE != 0,
125 is_sealed: flags & FLAG_SEALED != 0,
126 is_abstract: flags & FLAG_ABSTRACT != 0,
127 superclass,
128 traits,
129 })
130}
131
132fn find_primary_symbol<'a>(
141 reader: &'a ScalaSignatureReader,
142 symbols: &[(usize, &'a super::signature::SignatureEntry)],
143) -> Option<(usize, &'a super::signature::SignatureEntry)> {
144 for &(idx, entry) in symbols {
147 if let Some(info) = reader.read_symbol_info(entry)
148 && let Some(name) = reader.read_name(info.name_index)
149 && !name.is_empty()
150 && !name.starts_with('$')
151 && !name.contains("$anon")
152 {
153 return Some((idx, entry));
154 }
155 }
156
157 symbols.first().map(|&(idx, entry)| (idx, entry))
159}
160
161fn determine_kind(
163 tag: u8,
164 flags: u64,
165 reader: &ScalaSignatureReader,
166 name_index: usize,
167) -> ScalaClassKind {
168 if tag == TAG_MODULE_SYM {
170 if let Some(name) = reader.read_name(name_index)
172 && name == "package"
173 {
174 return ScalaClassKind::PackageObject;
175 }
176 return ScalaClassKind::Object;
177 }
178
179 if flags & FLAG_TRAIT != 0 || flags & FLAG_INTERFACE != 0 {
181 return ScalaClassKind::Trait;
182 }
183
184 ScalaClassKind::Class
185}
186
187fn determine_visibility(flags: u64) -> ScalaVisibility {
189 if flags & FLAG_PRIVATE != 0 {
190 ScalaVisibility::Private
191 } else if flags & FLAG_PROTECTED != 0 {
192 ScalaVisibility::Protected
193 } else {
194 ScalaVisibility::Public
195 }
196}
197
198fn extract_hierarchy(
209 reader: &ScalaSignatureReader,
210 _primary_index: usize,
211) -> (Option<String>, Vec<String>) {
212 let ext_refs = reader.ext_refs();
213
214 let mut superclass: Option<String> = None;
215 let mut traits = Vec::new();
216
217 const SKIP_NAMES: &[&str] = &[
219 "Object",
220 "AnyRef",
221 "Any",
222 "Serializable",
223 "Product",
224 "Equals",
225 "<empty>",
226 "java.lang.Object",
227 "scala.AnyRef",
228 ];
229
230 for &(_idx, entry) in &ext_refs {
231 if entry.tag != TAG_EXT_REF && entry.tag != TAG_EXT_MOD_CLASS_REF {
232 continue;
233 }
234
235 let mut pos = 0;
237 let Some(name_ref) = super::signature::read_nat(&entry.data, &mut pos) else {
238 continue;
239 };
240 let Some(name) = reader.read_name(name_ref as usize) else {
241 continue;
242 };
243
244 if SKIP_NAMES.contains(&name.as_str()) {
246 continue;
247 }
248
249 if name.starts_with('$') || name.contains("$anon") || name.is_empty() {
251 continue;
252 }
253
254 if entry.tag == TAG_EXT_REF {
258 if superclass.is_none() {
259 superclass = Some(name);
260 } else if !traits.contains(&name) {
261 traits.push(name);
262 }
263 } else if entry.tag == TAG_EXT_MOD_CLASS_REF && !traits.contains(&name) {
264 traits.push(name);
268 }
269 }
270
271 (superclass, traits)
272}
273
274#[cfg(test)]
279mod tests {
280 use super::*;
281 use crate::scala::signature::{
282 FLAG_MODULE, TAG_CLASS_SYM, TAG_EXT_REF, TAG_MODULE_SYM, TAG_NONE_SYM, TAG_TERM_NAME,
283 TAG_TYPE_NAME,
284 };
285
286 fn encode_nat(mut value: u64) -> Vec<u8> {
290 let mut bytes = Vec::new();
291 loop {
292 let mut byte = (value & 0x7F) as u8;
293 value >>= 7;
294 if value != 0 {
295 byte |= 0x80;
296 }
297 bytes.push(byte);
298 if value == 0 {
299 break;
300 }
301 }
302 bytes
303 }
304
305 fn build_entry(tag: u8, data: &[u8]) -> Vec<u8> {
307 let mut entry = vec![tag];
308 entry.extend(encode_nat(data.len() as u64));
309 entry.extend_from_slice(data);
310 entry
311 }
312
313 fn build_signature(entries: Vec<Vec<u8>>) -> Vec<u8> {
315 let mut buf = vec![5, 0]; buf.extend(encode_nat(entries.len() as u64));
317 for entry in entries {
318 buf.extend(entry);
319 }
320 buf
321 }
322
323 fn build_sym_data(name_ref: u64, owner_ref: u64, flags: u64, info_ref: u64) -> Vec<u8> {
325 let mut data = Vec::new();
326 data.extend(encode_nat(name_ref));
327 data.extend(encode_nat(owner_ref));
328 data.extend(encode_nat(flags));
329 data.extend(encode_nat(info_ref));
330 data
331 }
332
333 fn stub_from_entries(entries: Vec<Vec<u8>>) -> ScalaSignatureStub {
335 ScalaSignatureStub {
336 bytes: build_signature(entries),
337 major_version: 5,
338 minor_version: 0,
339 }
340 }
341
342 fn simple_class_stub(name: &str, flags: u64) -> ScalaSignatureStub {
344 let name_entry = build_entry(TAG_TYPE_NAME, name.as_bytes());
345 let owner = build_entry(TAG_NONE_SYM, &[]);
346 let sym_data = build_sym_data(0, 1, flags, 0);
347 let cls = build_entry(TAG_CLASS_SYM, &sym_data);
348 stub_from_entries(vec![name_entry, owner, cls])
349 }
350
351 fn simple_module_stub(name: &str, flags: u64) -> ScalaSignatureStub {
353 let name_entry = build_entry(TAG_TERM_NAME, name.as_bytes());
354 let owner = build_entry(TAG_NONE_SYM, &[]);
355 let sym_data = build_sym_data(0, 1, flags, 0);
356 let module = build_entry(TAG_MODULE_SYM, &sym_data);
357 stub_from_entries(vec![name_entry, owner, module])
358 }
359
360 #[test]
363 fn detect_trait() {
364 let stub = simple_class_stub("Functor", FLAG_TRAIT | FLAG_INTERFACE | FLAG_ABSTRACT);
365 let meta = decode_scala_signature(&stub).unwrap();
366 assert_eq!(meta.kind, ScalaClassKind::Trait);
367 assert!(meta.is_abstract);
368 }
369
370 #[test]
371 fn detect_trait_via_interface_flag_only() {
372 let stub = simple_class_stub("Showable", FLAG_INTERFACE | FLAG_ABSTRACT);
374 let meta = decode_scala_signature(&stub).unwrap();
375 assert_eq!(meta.kind, ScalaClassKind::Trait);
376 }
377
378 #[test]
379 fn detect_object() {
380 let stub = simple_module_stub("Config", FLAG_MODULE);
381 let meta = decode_scala_signature(&stub).unwrap();
382 assert_eq!(meta.kind, ScalaClassKind::Object);
383 assert!(!meta.is_case);
384 }
385
386 #[test]
387 fn detect_package_object() {
388 let stub = simple_module_stub("package", FLAG_MODULE);
389 let meta = decode_scala_signature(&stub).unwrap();
390 assert_eq!(meta.kind, ScalaClassKind::PackageObject);
391 }
392
393 #[test]
394 fn detect_regular_class() {
395 let stub = simple_class_stub("MyService", 0);
396 let meta = decode_scala_signature(&stub).unwrap();
397 assert_eq!(meta.kind, ScalaClassKind::Class);
398 assert!(!meta.is_case);
399 assert!(!meta.is_sealed);
400 assert!(!meta.is_abstract);
401 }
402
403 #[test]
406 fn detect_case_class() {
407 let stub = simple_class_stub("Point", FLAG_CASE);
408 let meta = decode_scala_signature(&stub).unwrap();
409 assert_eq!(meta.kind, ScalaClassKind::Class);
410 assert!(meta.is_case);
411 }
412
413 #[test]
414 fn detect_case_object() {
415 let stub = simple_module_stub("Nil", FLAG_MODULE | FLAG_CASE);
416 let meta = decode_scala_signature(&stub).unwrap();
417 assert_eq!(meta.kind, ScalaClassKind::Object);
418 assert!(meta.is_case);
419 }
420
421 #[test]
424 fn detect_sealed_trait() {
425 let stub = simple_class_stub(
426 "Option",
427 FLAG_SEALED | FLAG_ABSTRACT | FLAG_TRAIT | FLAG_INTERFACE,
428 );
429 let meta = decode_scala_signature(&stub).unwrap();
430 assert_eq!(meta.kind, ScalaClassKind::Trait);
431 assert!(meta.is_sealed);
432 assert!(meta.is_abstract);
433 }
434
435 #[test]
436 fn detect_sealed_class() {
437 let stub = simple_class_stub("Expr", FLAG_SEALED | FLAG_ABSTRACT);
438 let meta = decode_scala_signature(&stub).unwrap();
439 assert_eq!(meta.kind, ScalaClassKind::Class);
440 assert!(meta.is_sealed);
441 assert!(meta.is_abstract);
442 }
443
444 #[test]
447 fn detect_public_visibility() {
448 let stub = simple_class_stub("Public", 0);
449 let meta = decode_scala_signature(&stub).unwrap();
450 assert_eq!(meta.visibility, ScalaVisibility::Public);
451 }
452
453 #[test]
454 fn detect_private_visibility() {
455 let stub = simple_class_stub("Private", FLAG_PRIVATE);
456 let meta = decode_scala_signature(&stub).unwrap();
457 assert_eq!(meta.visibility, ScalaVisibility::Private);
458 }
459
460 #[test]
461 fn detect_protected_visibility() {
462 let stub = simple_class_stub("Protected", FLAG_PROTECTED);
463 let meta = decode_scala_signature(&stub).unwrap();
464 assert_eq!(meta.visibility, ScalaVisibility::Protected);
465 }
466
467 #[test]
470 fn detect_abstract_class() {
471 let stub = simple_class_stub("AbstractBase", FLAG_ABSTRACT);
472 let meta = decode_scala_signature(&stub).unwrap();
473 assert_eq!(meta.kind, ScalaClassKind::Class);
474 assert!(meta.is_abstract);
475 assert!(!meta.is_sealed);
476 assert!(!meta.is_case);
477 }
478
479 #[test]
482 fn extract_superclass_from_ext_ref() {
483 let class_name = build_entry(TAG_TYPE_NAME, b"Child");
485 let owner = build_entry(TAG_NONE_SYM, &[]);
486 let sym_data = build_sym_data(0, 1, 0, 0);
487 let cls = build_entry(TAG_CLASS_SYM, &sym_data);
488
489 let parent_name = build_entry(TAG_TERM_NAME, b"Parent");
491 let mut ext_data = Vec::new();
492 ext_data.extend(encode_nat(3)); let ext = build_entry(TAG_EXT_REF, &ext_data);
494
495 let stub = stub_from_entries(vec![class_name, owner, cls, parent_name, ext]);
496 let meta = decode_scala_signature(&stub).unwrap();
497
498 assert_eq!(meta.superclass, Some("Parent".to_string()));
499 }
500
501 #[test]
502 fn skip_object_and_anyref_in_hierarchy() {
503 let class_name = build_entry(TAG_TYPE_NAME, b"Foo");
504 let owner = build_entry(TAG_NONE_SYM, &[]);
505 let sym_data = build_sym_data(0, 1, 0, 0);
506 let cls = build_entry(TAG_CLASS_SYM, &sym_data);
507
508 let obj_name = build_entry(TAG_TERM_NAME, b"Object");
510 let mut ext1_data = Vec::new();
511 ext1_data.extend(encode_nat(3));
512 let ext1 = build_entry(TAG_EXT_REF, &ext1_data);
513
514 let anyref_name = build_entry(TAG_TERM_NAME, b"AnyRef");
515 let mut ext2_data = Vec::new();
516 ext2_data.extend(encode_nat(5));
517 let ext2 = build_entry(TAG_EXT_REF, &ext2_data);
518
519 let stub = stub_from_entries(vec![
520 class_name,
521 owner,
522 cls,
523 obj_name,
524 ext1,
525 anyref_name,
526 ext2,
527 ]);
528 let meta = decode_scala_signature(&stub).unwrap();
529
530 assert_eq!(meta.superclass, None);
532 }
533
534 #[test]
537 fn unsupported_major_version_returns_none() {
538 let stub = ScalaSignatureStub {
539 bytes: vec![5, 0, 0], major_version: 4, minor_version: 0,
542 };
543 assert!(decode_scala_signature(&stub).is_none());
544 }
545
546 #[test]
547 fn malformed_bytes_returns_none() {
548 let stub = ScalaSignatureStub {
549 bytes: vec![5, 0, 1], major_version: 5,
551 minor_version: 0,
552 };
553 assert!(decode_scala_signature(&stub).is_none());
555 }
556
557 #[test]
558 fn empty_bytes_returns_none() {
559 let stub = ScalaSignatureStub {
560 bytes: vec![],
561 major_version: 5,
562 minor_version: 0,
563 };
564 assert!(decode_scala_signature(&stub).is_none());
565 }
566
567 #[test]
568 fn no_class_symbols_returns_none() {
569 let name = build_entry(TAG_TYPE_NAME, b"Foo");
571 let stub = stub_from_entries(vec![name]);
572 assert!(decode_scala_signature(&stub).is_none());
573 }
574
575 #[test]
576 fn anonymous_class_skipped_for_primary() {
577 let anon_name = build_entry(TAG_TYPE_NAME, b"$anon$1");
579 let owner = build_entry(TAG_NONE_SYM, &[]);
580 let sym_data = build_sym_data(0, 1, 0, 0);
581 let cls = build_entry(TAG_CLASS_SYM, &sym_data);
582
583 let stub = stub_from_entries(vec![anon_name, owner, cls]);
584 let meta = decode_scala_signature(&stub);
586 assert!(meta.is_some());
587 }
588
589 #[test]
592 fn sealed_abstract_case_class() {
593 let stub = simple_class_stub("Weird", FLAG_SEALED | FLAG_ABSTRACT | FLAG_CASE);
595 let meta = decode_scala_signature(&stub).unwrap();
596 assert_eq!(meta.kind, ScalaClassKind::Class);
597 assert!(meta.is_sealed);
598 assert!(meta.is_abstract);
599 assert!(meta.is_case);
600 }
601
602 #[test]
603 fn private_sealed_trait() {
604 let stub = simple_class_stub(
605 "Internal",
606 FLAG_PRIVATE | FLAG_SEALED | FLAG_TRAIT | FLAG_INTERFACE | FLAG_ABSTRACT,
607 );
608 let meta = decode_scala_signature(&stub).unwrap();
609 assert_eq!(meta.kind, ScalaClassKind::Trait);
610 assert_eq!(meta.visibility, ScalaVisibility::Private);
611 assert!(meta.is_sealed);
612 assert!(meta.is_abstract);
613 }
614}