rusty-javac 0.2.2

A Java compiler written in Rust.
Documentation
use crate::bytecode::BytecodeError;
use crate::diagnostics::{Diagnostic, SourceFile, render_diagnostics};
use crate::hir::lowering::LowerError;
use text_size::{TextRange, TextSize};

pub(super) fn render_lower_error(filename: &str, source: &str, error: &LowerError) -> Vec<String> {
    let diagnostic = Diagnostic::error(error.to_string(), lower_error_range(source, error))
        .with_code("L0001")
        .with_primary_label(lower_error_label(error))
        .with_help(lower_error_help(error));

    render_diagnostics(SourceFile::new(filename, source), &[diagnostic])
}

pub(super) fn render_bytecode_error(
    filename: &str,
    source: &str,
    error: &BytecodeError,
) -> Vec<String> {
    let Some(line) = error.line else {
        return vec![format!("{}: {}", filename, error)];
    };

    let range = line_range(source, line as usize, error.needle.as_deref());
    let mut diagnostic = Diagnostic::error(error.message.clone(), range)
        .with_code(error.code)
        .with_primary_label(
            error
                .label
                .clone()
                .unwrap_or_else(|| "failed to compile this expression".to_string()),
        );

    if let Some(help) = &error.help {
        diagnostic = diagnostic.with_help(help.as_str());
    }

    render_diagnostics(SourceFile::new(filename, source), &[diagnostic])
}

fn lower_error_range(source: &str, error: &LowerError) -> TextRange {
    match error {
        LowerError::UnknownImport {
            name,
            line,
            range: Some(range),
        } => validated_range(source, *range)
            .unwrap_or_else(|| line_range(source, *line as usize, Some(name.as_str()))),
        LowerError::UnknownImport {
            name,
            line,
            range: None,
        }
        | LowerError::UnknownType { name, line } => {
            line_range(source, *line as usize, Some(name.as_str()))
        }
        LowerError::VarRequiresInitializer { line } => line_range(source, *line as usize, None),
        LowerError::UnsupportedExpressionAt {
            line,
            range: Some(range),
        } => validated_range(source, *range)
            .unwrap_or_else(|| line_range(source, *line as usize, None)),
        LowerError::UnsupportedExpressionAt { line, range: None } => {
            line_range(source, *line as usize, None)
        }
        _ => source_start_range(source),
    }
}

fn source_start_range(source: &str) -> TextRange {
    let start = source
        .char_indices()
        .find(|(_, ch)| !ch.is_whitespace())
        .map(|(index, _)| index)
        .unwrap_or(0);
    let end = source[start..]
        .chars()
        .next()
        .map(|ch| start + ch.len_utf8())
        .unwrap_or(start + 1);
    byte_range(start, end)
}

fn line_range(source: &str, line: usize, needle: Option<&str>) -> TextRange {
    let (line_start, line_end) = line_byte_bounds(source, line);
    if let Some(needle) = needle
        && let Some(relative_start) = source[line_start..line_end].find(needle)
    {
        let start = line_start + relative_start;
        return byte_range(start, start + needle.len());
    }

    let start = line_start;
    let end = line_end.max(start + 1);
    byte_range(start, end)
}

fn byte_range(start: usize, end: usize) -> TextRange {
    TextRange::new(
        TextSize::from(start.min(u32::MAX as usize) as u32),
        TextSize::from(end.min(u32::MAX as usize) as u32),
    )
}

fn validated_range(source: &str, range: TextRange) -> Option<TextRange> {
    let start = u32::from(range.start()) as usize;
    let end = u32::from(range.end()) as usize;
    (start < end && end <= source.len()).then_some(range)
}

fn line_byte_bounds(source: &str, target_line: usize) -> (usize, usize) {
    let mut current_line = 1;
    let mut line_start = 0;

    for (index, ch) in source.char_indices() {
        if current_line == target_line {
            let line_end = source[index..]
                .find('\n')
                .map(|offset| index + offset)
                .unwrap_or(source.len());
            return (line_start, line_end);
        }

        if ch == '\n' {
            current_line += 1;
            line_start = index + 1;
        }
    }

    if current_line == target_line {
        (line_start, source.len())
    } else {
        (source.len(), source.len())
    }
}

fn lower_error_label(error: &LowerError) -> &'static str {
    match error {
        LowerError::ExpectedSingleTopLevelClass => "missing class declaration",
        LowerError::UnsupportedExpression | LowerError::UnsupportedExpressionAt { .. } => {
            "unsupported expression here"
        }
        LowerError::PatternVariableOutOfScope(_) => "pattern variable is not in scope",
        LowerError::MissingClassName => "class name is missing",
        LowerError::MissingMethodName => "name is missing",
        LowerError::MissingType => "type is missing",
        LowerError::VarRequiresInitializer { .. } => "initializer is missing",
        LowerError::MissingImportName => "import name is missing",
        LowerError::UnknownImport { .. } => "unresolved import",
        LowerError::UnknownType { .. } => "unresolved type",
        LowerError::UnsupportedTypeDeclaration => "unsupported declaration",
        LowerError::UnsupportedClassMember => "unsupported member",
        LowerError::ExpectedCompilationUnit => "expected Java source",
    }
}

fn lower_error_help(error: &LowerError) -> &'static str {
    match error {
        LowerError::ExpectedSingleTopLevelClass => "add one top-level class declaration",
        LowerError::UnsupportedExpression | LowerError::UnsupportedExpressionAt { .. } => {
            "simplify the expression or add compiler support for it"
        }
        LowerError::PatternVariableOutOfScope(_) => {
            "move the pattern variable use into the guarded branch"
        }
        LowerError::MissingClassName => "add an identifier after the class keyword",
        LowerError::MissingMethodName => "add the missing identifier",
        LowerError::MissingType => "add a valid Java type",
        LowerError::VarRequiresInitializer { .. } => {
            "add an initializer or write the explicit type"
        }
        LowerError::MissingImportName => "add a qualified import name",
        LowerError::UnknownImport { .. } => {
            "check the import spelling or add the class, jar, or source directory with --class-path"
        }
        LowerError::UnknownType { .. } => {
            "import the type, use a java.lang type, or add it with --class-path"
        }
        LowerError::UnsupportedTypeDeclaration => "use a class declaration",
        LowerError::UnsupportedClassMember => "remove or simplify this class member",
        LowerError::ExpectedCompilationUnit => "provide a Java compilation unit",
    }
}