rusty-javac 0.2.3

A Java compiler written in Rust.
Documentation
use crate::ast::{AstNode, JavaSyntaxKind, JavaSyntaxNode, RecordDecl};
use crate::call_resolver::ClassCatalog;
use crate::hir::lowering::expr::BodyBuilder;
use crate::hir::lowering::member::{MemberOptions, lower_members};
use crate::hir::lowering::signature::{class_signature, lower_type_params};
use crate::hir::lowering::stmt::lower_block;
use crate::hir::lowering::syntax::{last_ident, source_line};
use crate::hir::lowering::types::{TypeResolver, lower_type_with_vars};
use crate::hir::lowering::unit::internal_class_name;
use crate::hir::lowering::{LowerError, LowerResult};
use crate::hir::*;
use crate::ty::{MethodSig, Ty};
use std::collections::HashSet;
use ustr::Ustr;

pub(super) fn lower_record_decl(
    record: RecordDecl,
    access_flags: u16,
    modifiers: Option<&JavaSyntaxNode>,
    package: Option<&Package>,
    imports: &[Import],
    catalog: &ClassCatalog,
) -> LowerResult<TypeDecl> {
    let name = record.name().ok_or(LowerError::MissingClassName)?;
    let internal_name = internal_class_name(package, name.text());
    let resolver = TypeResolver::for_class(package, imports, &internal_name, catalog)?;
    let annotations =
        crate::hir::lowering::annotation::lower_annotation_uses(modifiers, package, &resolver)?;
    let type_params = lower_type_params(record.syntax(), &resolver)?;
    let generic_signature = class_signature(record.syntax(), &type_params, &resolver)?;
    let type_vars = type_params
        .iter()
        .map(|param| param.name)
        .collect::<HashSet<_>>();
    let components = lower_record_components(record.syntax(), &type_vars, &resolver)?;

    let mut members = record
        .body()
        .map(|body| {
            lower_members(
                body.syntax(),
                &type_params,
                &resolver,
                Some(Ustr::from(&internal_name)),
                MemberOptions {
                    lower_constructors: false,
                    ..MemberOptions::default()
                },
            )
        })
        .transpose()?
        .unwrap_or_default();

    let mut fields = record_component_fields(&components);
    fields.append(&mut members.fields);

    let mut methods = Vec::new();
    methods.push(record_constructor(
        record.syntax(),
        record.body().as_ref().map(AstNode::syntax),
        &components,
        &resolver,
    )?);
    methods.extend(record_accessors(&components, &members.methods));
    methods.append(&mut members.methods);

    Ok(TypeDecl {
        id: HirId(0),
        name: Ustr::from(&internal_name),
        kind: TypeDeclKind::Record,
        access_flags: access_flags | crate::classfile::ACC_FINAL,
        super_class: Some(Ty::class("java/lang/Record")),
        interfaces: Vec::new(),
        type_params,
        generic_signature,
        fields,
        methods,
        inner_types: members.inner_types,
        anonymous: None,
        record_components: components,
        annotations,
    })
}

fn lower_record_components(
    record: &JavaSyntaxNode,
    type_vars: &HashSet<Ustr>,
    resolver: &TypeResolver,
) -> LowerResult<Vec<RecordComponentDecl>> {
    let Some(list) = record
        .children()
        .find(|child| child.kind() == JavaSyntaxKind::RecordComponentList)
    else {
        return Ok(Vec::new());
    };

    list.children()
        .filter(|child| child.kind() == JavaSyntaxKind::RecordComponent)
        .map(|component| {
            let ty_node = component
                .children()
                .find(|child| child.kind() == JavaSyntaxKind::Type)
                .ok_or(LowerError::MissingType)?;
            let name = last_ident(&component).ok_or(LowerError::MissingMethodName)?;
            Ok(RecordComponentDecl {
                name: Ustr::from(name.text()),
                ty: lower_type_with_vars(&ty_node, type_vars, resolver)?,
                generic_signature: None,
            })
        })
        .collect()
}

fn record_component_fields(components: &[RecordComponentDecl]) -> Vec<FieldDecl> {
    components
        .iter()
        .enumerate()
        .map(|(index, component)| FieldDecl {
            id: HirId(index as u32 + 1),
            name: component.name,
            ty: component.ty.clone(),
            access_flags: crate::classfile::ACC_PRIVATE | crate::classfile::ACC_FINAL,
            generic_signature: component.generic_signature.clone(),
            body: Body::default(),
            initializer: None,
        })
        .collect()
}

fn record_constructor(
    record: &JavaSyntaxNode,
    body: Option<&JavaSyntaxNode>,
    components: &[RecordComponentDecl],
    resolver: &TypeResolver,
) -> LowerResult<MethodDecl> {
    let params = components
        .iter()
        .map(|component| ParamDecl {
            name: component.name,
            ty: component.ty.clone(),
        })
        .collect::<Vec<_>>();
    let mut body_builder = BodyBuilder::new(resolver.clone());
    for param in &params {
        body_builder.define_local(param.name, param.ty.clone());
    }

    let mut stmts = compact_constructor_stmts(body, &mut body_builder)?;
    for component in components {
        stmts.push(assign_component_stmt(
            &mut body_builder,
            component,
            source_line(record),
        ));
    }

    let signature = MethodSig::new(
        Ustr::from("<init>"),
        params.iter().map(|param| param.ty.clone()).collect(),
        Ty::Void,
    );

    Ok(MethodDecl {
        id: HirId(1),
        name: Ustr::from("<init>"),
        params,
        signature,
        access_flags: crate::classfile::ACC_PUBLIC,
        source_line: Some(source_line(record)),
        generic_signature: None,
        throws: Vec::new(),
        body: body_builder.body,
        root_block: Some(Block { stmts }),
        constructor_call: None,
    })
}

fn compact_constructor_stmts(
    body: Option<&JavaSyntaxNode>,
    body_builder: &mut BodyBuilder,
) -> LowerResult<Vec<StmtId>> {
    let Some(constructor) = body
        .into_iter()
        .flat_map(JavaSyntaxNode::children)
        .find(|child| {
            child.kind() == JavaSyntaxKind::ConstructorDecl
                && child
                    .children()
                    .all(|node| node.kind() != JavaSyntaxKind::FormalParamList)
        })
    else {
        return Ok(Vec::new());
    };
    let Some(block) = constructor
        .descendants()
        .find(|node| node.kind() == JavaSyntaxKind::Block)
    else {
        return Ok(Vec::new());
    };
    Ok(lower_block(&block, body_builder)?.stmts)
}

fn assign_component_stmt(
    body: &mut BodyBuilder,
    component: &RecordComponentDecl,
    line: u16,
) -> StmtId {
    let this = body.alloc_expr(Expr::This);
    let target = body.alloc_expr(Expr::FieldAccess {
        target: this,
        field: component.name,
    });
    let value = body.alloc_expr(Expr::Ident(component.name));
    let assign = body.alloc_expr(Expr::Assign {
        target,
        op: AssignOp::Plain,
        value,
    });
    body.alloc_stmt_at(Stmt::Expr(assign), line)
}

fn record_accessors(
    components: &[RecordComponentDecl],
    existing_methods: &[MethodDecl],
) -> Vec<MethodDecl> {
    components
        .iter()
        .enumerate()
        .filter(|(_, component)| {
            !existing_methods
                .iter()
                .any(|method| method.name == component.name && method.params.is_empty())
        })
        .map(|(index, component)| record_accessor(component, index as u32 + 2))
        .collect()
}

fn record_accessor(component: &RecordComponentDecl, id: u32) -> MethodDecl {
    let mut body = Body::default();
    let this = body.exprs.alloc(Expr::This);
    let field = body.exprs.alloc(Expr::FieldAccess {
        target: this,
        field: component.name,
    });
    let stmt = body.stmts.alloc(Stmt::Return(Some(field)));
    body.stmt_lines.insert(stmt, 1);

    MethodDecl {
        id: HirId(id),
        name: component.name,
        params: Vec::new(),
        signature: MethodSig::new(component.name, Vec::new(), component.ty.clone()),
        access_flags: crate::classfile::ACC_PUBLIC,
        source_line: Some(1),
        generic_signature: None,
        throws: Vec::new(),
        body,
        root_block: Some(Block { stmts: vec![stmt] }),
        constructor_call: None,
    }
}