1mod attributes;
2mod builder;
3mod descriptor;
4mod generation_context;
5mod member_lowering;
6mod naming;
7
8use rajac_ast::{Ast, AstArena, ClassDeclId};
9use rajac_base::result::RajacResult;
10use rajac_symbols::SymbolTable;
11use ristretto_classfile::ClassFile;
12
13pub use generation_context::GeneratedClassFiles;
14
15use builder::{classfile_from_class_decl_with_context, emit_classfiles_for_class};
16use generation_context::ClassfileGenerationContext;
17use naming::internal_class_name;
18
19pub(crate) use attributes::{
20 build_inner_classes_attribute, class_access_flags, exceptions_attribute_from_ast_types,
21 has_modifier,
22};
23pub(crate) use descriptor::{
24 constructor_to_descriptor, method_to_descriptor, type_to_descriptor,
25 type_to_internal_class_name,
26};
27pub(crate) use generation_context::NestedClassInfo;
28pub(crate) use member_lowering::{
29 constructor_from_ast, enum_constructor_from_ast, field_from_ast, method_from_ast,
30};
31pub(crate) use naming::collect_nested_class_infos;
32
33pub fn generate_classfiles(
34 ast: &Ast,
35 arena: &AstArena,
36 type_arena: &rajac_types::TypeArena,
37 symbol_table: &SymbolTable,
38) -> RajacResult<Vec<ClassFile>> {
39 Ok(generate_classfiles_with_report(ast, arena, type_arena, symbol_table)?.class_files)
40}
41
42pub fn generate_classfiles_with_report(
43 ast: &Ast,
44 arena: &AstArena,
45 type_arena: &rajac_types::TypeArena,
46 symbol_table: &SymbolTable,
47) -> RajacResult<GeneratedClassFiles> {
48 let mut class_files = Vec::new();
49 let mut unsupported_features = Vec::new();
50 let mut generation_context = ClassfileGenerationContext {
51 type_arena,
52 symbol_table,
53 unsupported_features: &mut unsupported_features,
54 };
55 for class_id in &ast.classes {
56 let class = arena.class_decl(*class_id);
57 let internal_name = internal_class_name(ast, &class.name, symbol_table);
58 emit_classfiles_for_class(
59 arena,
60 *class_id,
61 internal_name.into(),
62 None,
63 &mut class_files,
64 &mut generation_context,
65 )?;
66 }
67 Ok(GeneratedClassFiles {
68 class_files,
69 unsupported_features,
70 })
71}
72
73pub fn classfile_from_class_decl(
74 ast: &Ast,
75 arena: &AstArena,
76 class_id: ClassDeclId,
77 type_arena: &rajac_types::TypeArena,
78 symbol_table: &SymbolTable,
79) -> RajacResult<ClassFile> {
80 let class = arena.class_decl(class_id);
81 let this_internal_name = internal_class_name(ast, &class.name, symbol_table);
82 let mut unsupported_features = Vec::new();
83 let mut generation_context = ClassfileGenerationContext {
84 type_arena,
85 symbol_table,
86 unsupported_features: &mut unsupported_features,
87 };
88 classfile_from_class_decl_with_context(
89 arena,
90 class_id,
91 &this_internal_name,
92 None,
93 &[],
94 &mut generation_context,
95 )
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101 use rajac_ast::{
102 Ast, AstArena, AstType, ClassDecl, ClassKind, ClassMember, Constructor as AstConstructor,
103 EnumEntry, Method, Modifiers, PackageDecl, Param, PrimitiveType, QualifiedName,
104 };
105 use rajac_base::shared_string::SharedString;
106 use rajac_types::Ident;
107 use ristretto_classfile::attributes::{Attribute, NestedClassAccessFlags};
108
109 #[test]
110 fn generates_minimal_abstract_method_without_code_attribute() -> RajacResult<()> {
111 let mut arena = AstArena::new();
112 let mut ast = Ast::new(SharedString::new("test"));
113 let type_arena = rajac_types::TypeArena::new();
114 let symbol_table = SymbolTable::new();
115
116 let void_ty = arena.alloc_type(AstType::Primitive {
117 kind: PrimitiveType::Void,
118 ty: rajac_types::TypeId::INVALID,
119 });
120 let int_ty = arena.alloc_type(AstType::Primitive {
121 kind: PrimitiveType::Int,
122 ty: rajac_types::TypeId::INVALID,
123 });
124
125 let param_id = arena.alloc_param(Param {
126 ty: int_ty,
127 name: Ident::new(SharedString::new("x")),
128 varargs: false,
129 });
130
131 let method = Method {
132 name: Ident::new(SharedString::new("f")),
133 params: vec![param_id],
134 return_ty: void_ty,
135 body: None,
136 throws: vec![],
137 modifiers: Modifiers(Modifiers::PUBLIC),
138 };
139
140 let member_id = arena.alloc_class_member(ClassMember::Method(method));
141
142 let class_id = arena.alloc_class_decl(ClassDecl {
143 kind: ClassKind::Interface,
144 name: Ident::new(SharedString::new("Foo")),
145 type_params: vec![],
146 extends: None,
147 implements: vec![],
148 permits: vec![],
149 enum_entries: vec![],
150 members: vec![member_id],
151 modifiers: Modifiers(Modifiers::PUBLIC),
152 });
153
154 ast.classes.push(class_id);
155
156 let mut class_files = generate_classfiles(&ast, &arena, &type_arena, &symbol_table)?;
157 assert_eq!(class_files.len(), 1);
158
159 let class_file = class_files.pop().unwrap();
160 class_file.verify()?;
161
162 assert_eq!(class_file.methods.len(), 1);
163 assert!(class_file.methods[0].attributes.is_empty());
164
165 Ok(())
166 }
167
168 #[test]
169 fn generates_bytecode_for_methods_with_bodies() -> RajacResult<()> {
170 let mut arena = AstArena::new();
171 let mut ast = Ast::new(SharedString::new("test"));
172 let type_arena = rajac_types::TypeArena::new();
173 let symbol_table = SymbolTable::new();
174
175 let void_ty = arena.alloc_type(AstType::Primitive {
176 kind: PrimitiveType::Void,
177 ty: rajac_types::TypeId::INVALID,
178 });
179 let empty_block = arena.alloc_stmt(rajac_ast::Stmt::Block(vec![]));
180
181 let method = Method {
182 name: Ident::new(SharedString::new("g")),
183 params: vec![],
184 return_ty: void_ty,
185 body: Some(empty_block),
186 throws: vec![],
187 modifiers: Modifiers(Modifiers::PUBLIC),
188 };
189
190 let member_id = arena.alloc_class_member(ClassMember::Method(method));
191
192 let class_id = arena.alloc_class_decl(ClassDecl {
193 kind: ClassKind::Class,
194 name: Ident::new(SharedString::new("Foo")),
195 type_params: vec![],
196 extends: None,
197 implements: vec![],
198 permits: vec![],
199 enum_entries: vec![],
200 members: vec![member_id],
201 modifiers: Modifiers(Modifiers::PUBLIC),
202 });
203
204 ast.classes.push(class_id);
205
206 let mut class_files = generate_classfiles(&ast, &arena, &type_arena, &symbol_table)?;
207 assert_eq!(class_files.len(), 1);
208
209 let class_file = class_files.pop().unwrap();
210 class_file.verify()?;
211 assert_eq!(class_file.methods.len(), 2);
212
213 let method_with_body = class_file
214 .methods
215 .iter()
216 .find(|m| class_file.constant_pool.try_get_utf8(m.name_index).ok() == Some("g"))
217 .expect("method 'g' should be present");
218
219 assert!(!method_with_body.attributes.is_empty());
220 let has_code = method_with_body
221 .attributes
222 .iter()
223 .any(|attr| matches!(attr, Attribute::Code { .. }));
224 assert!(has_code);
225
226 Ok(())
227 }
228
229 #[test]
230 fn emits_inner_class_files_and_attributes() -> RajacResult<()> {
231 let mut arena = AstArena::new();
232 let mut ast = Ast::new(SharedString::new("test"));
233 let type_arena = rajac_types::TypeArena::new();
234 let symbol_table = SymbolTable::new();
235 ast.package = Some(PackageDecl {
236 name: QualifiedName::new(vec![SharedString::new("p")]),
237 });
238
239 let inner_id = arena.alloc_class_decl(ClassDecl {
240 kind: ClassKind::Class,
241 name: Ident::new(SharedString::new("Inner")),
242 type_params: vec![],
243 extends: None,
244 implements: vec![],
245 permits: vec![],
246 enum_entries: vec![],
247 members: vec![],
248 modifiers: Modifiers(Modifiers::PRIVATE),
249 });
250
251 let inner_member_id = arena.alloc_class_member(ClassMember::NestedClass(inner_id));
252
253 let outer_id = arena.alloc_class_decl(ClassDecl {
254 kind: ClassKind::Class,
255 name: Ident::new(SharedString::new("Outer")),
256 type_params: vec![],
257 extends: None,
258 implements: vec![],
259 permits: vec![],
260 enum_entries: vec![],
261 members: vec![inner_member_id],
262 modifiers: Modifiers(Modifiers::PUBLIC),
263 });
264
265 ast.classes.push(outer_id);
266
267 let class_files = generate_classfiles(&ast, &arena, &type_arena, &symbol_table)?;
268 assert_eq!(class_files.len(), 2);
269
270 let mut outer = None;
271 let mut inner = None;
272
273 for class_file in &class_files {
274 match class_file.class_name()? {
275 "p/Outer" => outer = Some(class_file),
276 "p/Outer$Inner" => inner = Some(class_file),
277 other => panic!("unexpected class emitted: {other}"),
278 }
279 }
280
281 let outer = outer.expect("outer class not emitted");
282 let inner = inner.expect("inner class not emitted");
283
284 let outer_inner_attr = outer
285 .attributes
286 .iter()
287 .find_map(|attr| match attr {
288 Attribute::InnerClasses { classes, .. } => Some(classes),
289 _ => None,
290 })
291 .expect("outer class missing InnerClasses attribute");
292
293 let outer_entry = outer_inner_attr
294 .iter()
295 .find(|entry| {
296 outer
297 .constant_pool
298 .try_get_class(entry.class_info_index)
299 .ok()
300 == Some("p/Outer$Inner")
301 })
302 .expect("outer class missing inner class entry");
303
304 assert_eq!(
305 outer
306 .constant_pool
307 .try_get_class(outer_entry.outer_class_info_index)?,
308 "p/Outer"
309 );
310 assert_eq!(
311 outer.constant_pool.try_get_utf8(outer_entry.name_index)?,
312 "Inner"
313 );
314 assert!(
315 outer_entry
316 .access_flags
317 .contains(NestedClassAccessFlags::PRIVATE)
318 );
319
320 let inner_attr = inner
321 .attributes
322 .iter()
323 .find_map(|attr| match attr {
324 Attribute::InnerClasses { classes, .. } => Some(classes),
325 _ => None,
326 })
327 .expect("inner class missing InnerClasses attribute");
328
329 let inner_entry = inner_attr
330 .iter()
331 .find(|entry| {
332 inner
333 .constant_pool
334 .try_get_class(entry.class_info_index)
335 .ok()
336 == Some("p/Outer$Inner")
337 })
338 .expect("inner class missing self entry");
339
340 assert_eq!(
341 inner
342 .constant_pool
343 .try_get_class(inner_entry.outer_class_info_index)?,
344 "p/Outer"
345 );
346 assert_eq!(
347 inner.constant_pool.try_get_utf8(inner_entry.name_index)?,
348 "Inner"
349 );
350
351 Ok(())
352 }
353
354 #[test]
355 fn emits_nested_enum_class_files() -> RajacResult<()> {
356 let mut arena = AstArena::new();
357 let mut ast = Ast::new(SharedString::new("test"));
358 let type_arena = rajac_types::TypeArena::new();
359 let symbol_table = SymbolTable::new();
360 ast.package = Some(PackageDecl {
361 name: QualifiedName::new(vec![SharedString::new("p")]),
362 });
363
364 let inner_id = arena.alloc_class_decl(ClassDecl {
365 kind: ClassKind::Enum,
366 name: Ident::new(SharedString::new("Inner")),
367 type_params: vec![],
368 extends: None,
369 implements: vec![],
370 permits: vec![],
371 enum_entries: vec![EnumEntry {
372 name: Ident::new(SharedString::new("VALUE")),
373 args: vec![],
374 body: None,
375 }],
376 members: vec![],
377 modifiers: Modifiers(Modifiers::PRIVATE),
378 });
379
380 let inner_member_id = arena.alloc_class_member(ClassMember::NestedEnum(inner_id));
381
382 let outer_id = arena.alloc_class_decl(ClassDecl {
383 kind: ClassKind::Class,
384 name: Ident::new(SharedString::new("Outer")),
385 type_params: vec![],
386 extends: None,
387 implements: vec![],
388 permits: vec![],
389 enum_entries: vec![],
390 members: vec![inner_member_id],
391 modifiers: Modifiers(Modifiers::PUBLIC),
392 });
393
394 ast.classes.push(outer_id);
395
396 let class_files = generate_classfiles(&ast, &arena, &type_arena, &symbol_table)?;
397 assert_eq!(class_files.len(), 2);
398 assert!(
399 class_files
400 .iter()
401 .any(|class_file| class_file.class_name().ok() == Some("p/Outer"))
402 );
403 assert!(
404 class_files
405 .iter()
406 .any(|class_file| class_file.class_name().ok() == Some("p/Outer$Inner"))
407 );
408
409 Ok(())
410 }
411
412 #[test]
413 fn emits_explicit_constructors_as_init_methods() -> RajacResult<()> {
414 let mut arena = AstArena::new();
415 let mut ast = Ast::new(SharedString::new("test"));
416 let type_arena = rajac_types::TypeArena::new();
417 let symbol_table = SymbolTable::new();
418
419 let int_ty = arena.alloc_type(AstType::Primitive {
420 kind: PrimitiveType::Int,
421 ty: rajac_types::TypeId::INVALID,
422 });
423 let param_id = arena.alloc_param(Param {
424 ty: int_ty,
425 name: Ident::new(SharedString::new("x")),
426 varargs: false,
427 });
428 let body = arena.alloc_stmt(rajac_ast::Stmt::Block(vec![]));
429 let constructor = AstConstructor {
430 name: Ident::new(SharedString::new("Foo")),
431 params: vec![param_id],
432 body: Some(body),
433 throws: vec![],
434 modifiers: Modifiers(Modifiers::PUBLIC),
435 };
436 let ctor_member_id = arena.alloc_class_member(ClassMember::Constructor(constructor));
437
438 let class_id = arena.alloc_class_decl(ClassDecl {
439 kind: ClassKind::Class,
440 name: Ident::new(SharedString::new("Foo")),
441 type_params: vec![],
442 extends: None,
443 implements: vec![],
444 permits: vec![],
445 enum_entries: vec![],
446 members: vec![ctor_member_id],
447 modifiers: Modifiers(Modifiers::PUBLIC),
448 });
449 ast.classes.push(class_id);
450
451 let mut class_files = generate_classfiles(&ast, &arena, &type_arena, &symbol_table)?;
452 let class_file = class_files.pop().unwrap();
453 class_file.verify()?;
454
455 assert_eq!(class_file.methods.len(), 1);
456 let constructor = &class_file.methods[0];
457 assert_eq!(
458 class_file
459 .constant_pool
460 .try_get_utf8(constructor.name_index)?,
461 "<init>"
462 );
463 assert_eq!(
464 class_file
465 .constant_pool
466 .try_get_utf8(constructor.descriptor_index)?,
467 "(I)V"
468 );
469 assert!(
470 constructor
471 .attributes
472 .iter()
473 .any(|attribute| matches!(attribute, Attribute::Code { .. }))
474 );
475
476 Ok(())
477 }
478
479 #[test]
480 fn emits_enum_fields_and_synthetic_methods() -> RajacResult<()> {
481 let mut arena = AstArena::new();
482 let mut ast = Ast::new(SharedString::new("test"));
483 let type_arena = rajac_types::TypeArena::new();
484 let symbol_table = SymbolTable::new();
485
486 let class_id = arena.alloc_class_decl(ClassDecl {
487 kind: ClassKind::Enum,
488 name: Ident::new(SharedString::new("Color")),
489 type_params: vec![],
490 extends: None,
491 implements: vec![],
492 permits: vec![],
493 enum_entries: vec![
494 EnumEntry {
495 name: Ident::new(SharedString::new("RED")),
496 args: vec![],
497 body: None,
498 },
499 EnumEntry {
500 name: Ident::new(SharedString::new("GREEN")),
501 args: vec![],
502 body: None,
503 },
504 ],
505 members: vec![],
506 modifiers: Modifiers(Modifiers::PUBLIC),
507 });
508 ast.classes.push(class_id);
509
510 let mut class_files = generate_classfiles(&ast, &arena, &type_arena, &symbol_table)?;
511 let class_file = class_files.pop().unwrap();
512 class_file.verify()?;
513
514 let field_names = class_file
515 .fields
516 .iter()
517 .map(|field| class_file.constant_pool.try_get_utf8(field.name_index))
518 .collect::<Result<Vec<_>, _>>()?;
519 assert!(field_names.contains(&"RED"));
520 assert!(field_names.contains(&"GREEN"));
521 assert!(field_names.contains(&"$VALUES"));
522
523 let method_names = class_file
524 .methods
525 .iter()
526 .map(|method| class_file.constant_pool.try_get_utf8(method.name_index))
527 .collect::<Result<Vec<_>, _>>()?;
528 assert!(method_names.contains(&"values"));
529 assert!(method_names.contains(&"valueOf"));
530 assert!(method_names.contains(&"<clinit>"));
531
532 Ok(())
533 }
534
535 #[test]
536 fn emits_exceptions_attribute_for_methods() -> RajacResult<()> {
537 let mut arena = AstArena::new();
538 let mut ast = Ast::new(SharedString::new("test"));
539 let mut symbol_table = SymbolTable::new();
540
541 let exception_ty_id = symbol_table.add_class(
542 "java.lang",
543 "Exception",
544 rajac_types::Type::class(
545 rajac_types::ClassType::new(SharedString::new("Exception"))
546 .with_package(SharedString::new("java.lang")),
547 ),
548 rajac_symbols::SymbolKind::Class,
549 );
550 let type_arena = symbol_table.type_arena().clone();
551 let void_ty = arena.alloc_type(AstType::Primitive {
552 kind: PrimitiveType::Void,
553 ty: rajac_types::TypeId::INVALID,
554 });
555 let throws_ty = arena.alloc_type(AstType::Simple {
556 name: SharedString::new("Exception"),
557 type_args: vec![],
558 ty: exception_ty_id,
559 });
560 let empty_block = arena.alloc_stmt(rajac_ast::Stmt::Block(vec![]));
561
562 let method = Method {
563 name: Ident::new(SharedString::new("g")),
564 params: vec![],
565 return_ty: void_ty,
566 body: Some(empty_block),
567 throws: vec![throws_ty],
568 modifiers: Modifiers(Modifiers::PUBLIC),
569 };
570
571 let member_id = arena.alloc_class_member(ClassMember::Method(method));
572 let class_id = arena.alloc_class_decl(ClassDecl {
573 kind: ClassKind::Class,
574 name: Ident::new(SharedString::new("Foo")),
575 type_params: vec![],
576 extends: None,
577 implements: vec![],
578 permits: vec![],
579 enum_entries: vec![],
580 members: vec![member_id],
581 modifiers: Modifiers(Modifiers::PUBLIC),
582 });
583 ast.classes.push(class_id);
584
585 let mut class_files = generate_classfiles(&ast, &arena, &type_arena, &symbol_table)?;
586 let class_file = class_files.pop().unwrap();
587 class_file.verify()?;
588
589 let method = class_file
590 .methods
591 .iter()
592 .find(|method| {
593 class_file
594 .constant_pool
595 .try_get_utf8(method.name_index)
596 .ok()
597 == Some("g")
598 })
599 .expect("method 'g' should be present");
600
601 let exceptions = method
602 .attributes
603 .iter()
604 .find_map(|attribute| match attribute {
605 Attribute::Exceptions {
606 exception_indexes, ..
607 } => Some(exception_indexes),
608 _ => None,
609 })
610 .expect("Exceptions attribute should be present");
611
612 assert_eq!(exceptions.len(), 1);
613 assert_eq!(
614 class_file.constant_pool.try_get_class(exceptions[0])?,
615 "java/lang/Exception"
616 );
617 Ok(())
618 }
619
620 #[test]
621 fn emits_exceptions_attribute_for_constructors() -> RajacResult<()> {
622 let mut arena = AstArena::new();
623 let mut ast = Ast::new(SharedString::new("test"));
624 let mut symbol_table = SymbolTable::new();
625
626 let exception_ty_id = symbol_table.add_class(
627 "java.lang",
628 "Exception",
629 rajac_types::Type::class(
630 rajac_types::ClassType::new(SharedString::new("Exception"))
631 .with_package(SharedString::new("java.lang")),
632 ),
633 rajac_symbols::SymbolKind::Class,
634 );
635 let type_arena = symbol_table.type_arena().clone();
636 let throws_ty = arena.alloc_type(AstType::Simple {
637 name: SharedString::new("Exception"),
638 type_args: vec![],
639 ty: exception_ty_id,
640 });
641 let body = arena.alloc_stmt(rajac_ast::Stmt::Block(vec![]));
642 let constructor = AstConstructor {
643 name: Ident::new(SharedString::new("Foo")),
644 params: vec![],
645 body: Some(body),
646 throws: vec![throws_ty],
647 modifiers: Modifiers(Modifiers::PUBLIC),
648 };
649 let ctor_member_id = arena.alloc_class_member(ClassMember::Constructor(constructor));
650
651 let class_id = arena.alloc_class_decl(ClassDecl {
652 kind: ClassKind::Class,
653 name: Ident::new(SharedString::new("Foo")),
654 type_params: vec![],
655 extends: None,
656 implements: vec![],
657 permits: vec![],
658 enum_entries: vec![],
659 members: vec![ctor_member_id],
660 modifiers: Modifiers(Modifiers::PUBLIC),
661 });
662 ast.classes.push(class_id);
663
664 let mut class_files = generate_classfiles(&ast, &arena, &type_arena, &symbol_table)?;
665 let class_file = class_files.pop().unwrap();
666 class_file.verify()?;
667
668 let constructor = class_file
669 .methods
670 .iter()
671 .find(|method| {
672 class_file
673 .constant_pool
674 .try_get_utf8(method.name_index)
675 .ok()
676 == Some("<init>")
677 })
678 .expect("constructor should be present");
679
680 let exceptions = constructor
681 .attributes
682 .iter()
683 .find_map(|attribute| match attribute {
684 Attribute::Exceptions {
685 exception_indexes, ..
686 } => Some(exception_indexes),
687 _ => None,
688 })
689 .expect("Exceptions attribute should be present");
690
691 assert_eq!(exceptions.len(), 1);
692 assert_eq!(
693 class_file.constant_pool.try_get_class(exceptions[0])?,
694 "java/lang/Exception"
695 );
696
697 Ok(())
698 }
699}