rusty-javac 0.2.3

A Java compiler written in Rust.
Documentation
use crate::classfile::MethodWriter;
use crate::ty::Ty;
use crate::ty::check::{boxing_type, unboxing_type};
use rust_asm::opcodes;

pub(crate) fn coerce(mw: &mut MethodWriter, from: &Ty, to: &Ty) {
    if from == to {
        return;
    }

    if emit_numeric_conversion(mw, from, to) {
        return;
    }

    if emit_boxing_conversion(mw, from, to) {
        return;
    }

    if to.erasure().is_primitive()
        && let Some(unboxed) = emit_unboxing_conversion(mw, from)
    {
        emit_numeric_conversion(mw, &unboxed, to);
    }
}

pub(crate) fn cast(mw: &mut MethodWriter, from: &Ty, to: &Ty) {
    if from == to {
        return;
    }

    if emit_numeric_conversion(mw, from, to) {
        return;
    }

    if let Some(name) = checkcast_target(to) {
        mw.visit_type_insn(opcodes::CHECKCAST, &name);
    }
}

fn emit_numeric_conversion(mw: &mut MethodWriter, from: &Ty, to: &Ty) -> bool {
    match (from.erasure(), to.erasure()) {
        (Ty::Int | Ty::Byte | Ty::Short | Ty::Char | Ty::Boolean, Ty::Long) => {
            mw.visit_insn(opcodes::I2L);
            true
        }
        (Ty::Int | Ty::Byte | Ty::Short | Ty::Char | Ty::Boolean, Ty::Float) => {
            mw.visit_insn(opcodes::I2F);
            true
        }
        (Ty::Int | Ty::Byte | Ty::Short | Ty::Char | Ty::Boolean, Ty::Double) => {
            mw.visit_insn(opcodes::I2D);
            true
        }
        (Ty::Long, Ty::Int | Ty::Byte | Ty::Short | Ty::Char | Ty::Boolean) => {
            mw.visit_insn(opcodes::L2I);
            true
        }
        (Ty::Long, Ty::Float) => {
            mw.visit_insn(opcodes::L2F);
            true
        }
        (Ty::Long, Ty::Double) => {
            mw.visit_insn(opcodes::L2D);
            true
        }
        (Ty::Float, Ty::Int | Ty::Byte | Ty::Short | Ty::Char | Ty::Boolean) => {
            mw.visit_insn(opcodes::F2I);
            true
        }
        (Ty::Float, Ty::Long) => {
            mw.visit_insn(opcodes::F2L);
            true
        }
        (Ty::Float, Ty::Double) => {
            mw.visit_insn(opcodes::F2D);
            true
        }
        (Ty::Double, Ty::Int | Ty::Byte | Ty::Short | Ty::Char | Ty::Boolean) => {
            mw.visit_insn(opcodes::D2I);
            true
        }
        (Ty::Double, Ty::Long) => {
            mw.visit_insn(opcodes::D2L);
            true
        }
        (Ty::Double, Ty::Float) => {
            mw.visit_insn(opcodes::D2F);
            true
        }
        (_, Ty::Byte) => {
            mw.visit_insn(opcodes::I2B);
            true
        }
        (_, Ty::Char) => {
            mw.visit_insn(opcodes::I2C);
            true
        }
        (_, Ty::Short) => {
            mw.visit_insn(opcodes::I2S);
            true
        }
        _ => false,
    }
}

fn emit_boxing_conversion(mw: &mut MethodWriter, from: &Ty, to: &Ty) -> bool {
    let Some(wrapper) = boxing_type(&from.erasure()) else {
        return false;
    };
    if !matches!(
        to.erasure(),
        Ty::Class(_) | Ty::TypeVar(_) | Ty::Wildcard(_)
    ) {
        return false;
    }

    let Ty::Class(owner) = wrapper else {
        return false;
    };
    mw.visit_method_insn(
        opcodes::INVOKESTATIC,
        owner.as_str(),
        "valueOf",
        &format!("({})L{};", from.erasure().descriptor(), owner.as_str()),
        false,
    );
    true
}

fn emit_unboxing_conversion(mw: &mut MethodWriter, from: &Ty) -> Option<Ty> {
    let unboxed = unboxing_type(&from.erasure())?;
    let (owner, method, descriptor) = match &unboxed {
        Ty::Boolean => ("java/lang/Boolean", "booleanValue", "()Z"),
        Ty::Byte => ("java/lang/Byte", "byteValue", "()B"),
        Ty::Char => ("java/lang/Character", "charValue", "()C"),
        Ty::Short => ("java/lang/Short", "shortValue", "()S"),
        Ty::Int => ("java/lang/Integer", "intValue", "()I"),
        Ty::Long => ("java/lang/Long", "longValue", "()J"),
        Ty::Float => ("java/lang/Float", "floatValue", "()F"),
        Ty::Double => ("java/lang/Double", "doubleValue", "()D"),
        _ => return None,
    };
    mw.visit_method_insn(opcodes::INVOKEVIRTUAL, owner, method, descriptor, false);
    Some(unboxed)
}

fn checkcast_target(ty: &Ty) -> Option<String> {
    match ty.erasure() {
        Ty::Class(name) => Some(name.to_string()),
        Ty::Array(_) => Some(ty.erasure().descriptor()),
        _ => None,
    }
}

pub(crate) fn pop_ty(mw: &mut MethodWriter, ty: &Ty) {
    if matches!(ty, Ty::Void) {
        return;
    }

    mw.visit_insn(if ty.size() == 2 {
        opcodes::POP2
    } else {
        opcodes::POP
    });
}

pub(crate) fn push_default_value(mw: &mut MethodWriter, ty: &Ty) {
    match ty {
        Ty::Void => {}
        Ty::Long => mw.visit_insn(opcodes::LCONST_0),
        Ty::Float => mw.visit_insn(opcodes::FCONST_0),
        Ty::Double => mw.visit_insn(opcodes::DCONST_0),
        Ty::Class(_) | Ty::Array(_) | Ty::TypeVar(_) | Ty::Wildcard(_) | Ty::Intersection(_) => {
            mw.visit_insn(opcodes::ACONST_NULL);
        }
        _ => mw.visit_insn(opcodes::ICONST_0),
    }
}

pub(super) fn dup_ty(mw: &mut MethodWriter, ty: &Ty) {
    mw.visit_insn(if ty.size() == 2 {
        opcodes::DUP2
    } else {
        opcodes::DUP
    });
}