lisette-semantics 0.2.17

Little language inspired by Rust that compiles to Go
Documentation
use diagnostics::LisetteDiagnostic;
use syntax::ast::{BinaryOperator, Expression, Literal};
use syntax::types::{CompoundKind, Type};

pub fn check_manual_is_empty(expression: &Expression, diagnostics: &mut Vec<LisetteDiagnostic>) {
    let Expression::Binary {
        operator,
        left,
        right,
        span,
        ..
    } = expression
    else {
        return;
    };

    use BinaryOperator::*;
    if !matches!(
        operator,
        Equal | NotEqual | LessThan | LessThanOrEqual | GreaterThan | GreaterThanOrEqual
    ) {
        return;
    }

    let (receiver, operator) = match (
        length_call_receiver(left.unwrap_parens()),
        length_call_receiver(right.unwrap_parens()),
    ) {
        (Some(receiver), None) if is_zero_literal(right.unwrap_parens()) => (receiver, *operator),
        (None, Some(receiver)) if is_zero_literal(left.unwrap_parens()) => {
            (receiver, flip_comparison(*operator))
        }
        _ => return,
    };

    if !type_has_is_empty(&receiver.get_type()) {
        return;
    }

    let negate = match operator {
        Equal | LessThanOrEqual => false,
        NotEqual | GreaterThan => true,
        _ => return,
    };

    let Some(receiver_text) = receiver.as_dotted_path() else {
        return;
    };

    let replacement = if negate {
        format!("!{receiver_text}.is_empty()")
    } else {
        format!("{receiver_text}.is_empty()")
    };

    diagnostics.push(diagnostics::lint::manual_is_empty(span, &replacement));
}

fn length_call_receiver(expression: &Expression) -> Option<&Expression> {
    let Expression::Call {
        expression: callee,
        args,
        ..
    } = expression
    else {
        return None;
    };

    if !args.is_empty() {
        return None;
    }

    let Expression::DotAccess {
        expression: receiver,
        member,
        ..
    } = callee.unwrap_parens()
    else {
        return None;
    };

    (member == "length").then_some(receiver.as_ref())
}

fn type_has_is_empty(ty: &Type) -> bool {
    ty.is_string()
        || ty.is_slice()
        || ty.is_map()
        || ty.is_channel()
        || ty.is_native(CompoundKind::Sender)
        || ty.is_receiver()
}

fn is_zero_literal(expression: &Expression) -> bool {
    matches!(
        expression,
        Expression::Literal {
            literal: Literal::Integer { value: 0, .. },
            ..
        }
    )
}

fn flip_comparison(operator: BinaryOperator) -> BinaryOperator {
    use BinaryOperator::*;
    match operator {
        LessThan => GreaterThan,
        GreaterThan => LessThan,
        LessThanOrEqual => GreaterThanOrEqual,
        GreaterThanOrEqual => LessThanOrEqual,
        other => other,
    }
}