lisette-semantics 0.4.1

Little language inspired by Rust that compiles to Go
Documentation
use crate::passes::walk::NodeCtx;
use syntax::ast::{Expression, MatchArm, MatchOrigin, Pattern, Span};
use syntax::types::{Type, unqualified_name};

use crate::is_trivial_expression;
use crate::store::Store;

pub fn check_match_as_if_let(expression: &Expression, ctx: &NodeCtx) {
    let Expression::Match {
        subject,
        arms,
        origin,
        span,
        ..
    } = expression
    else {
        return;
    };

    if matches!(origin, MatchOrigin::IfLet { .. }) {
        return;
    }

    if arms.len() != 2 {
        return;
    }

    let (first, second) = (&arms[0], &arms[1]);

    if first.has_guard() || second.has_guard() {
        return;
    }

    let (meaningful, dismissal) = if is_meaningful_arm(first) && is_trailing_dismissal(second) {
        (first, second)
    } else if is_meaningful_arm(second) && is_leading_dismissal(first, second) {
        (second, first)
    } else {
        return;
    };

    if !is_catchall(&dismissal.pattern) && enum_has_extra_variants(&subject.get_type(), ctx.store) {
        return;
    }

    let pattern_span = meaningful.pattern.get_span();
    let Some(pattern_text) = ctx
        .source
        .get(pattern_span.byte_offset as usize..pattern_span.end() as usize)
    else {
        return;
    };

    let match_keyword_span = Span::new(span.file_id, span.byte_offset, 5);

    ctx.sink.push(diagnostics::lint::match_as_if_let(
        &match_keyword_span,
        pattern_text,
    ));
}

fn is_meaningful_arm(arm: &MatchArm) -> bool {
    matches!(&arm.pattern, Pattern::EnumVariant { .. }) && !is_trivial_expression(&arm.expression)
}

fn is_catchall(pattern: &Pattern) -> bool {
    matches!(
        pattern,
        Pattern::WildCard { .. } | Pattern::Identifier { .. }
    )
}

fn is_trailing_dismissal(arm: &MatchArm) -> bool {
    let dismissable =
        is_catchall(&arm.pattern) || matches!(&arm.pattern, Pattern::EnumVariant { .. });
    dismissable && is_trivial_expression(&arm.expression)
}

fn is_leading_dismissal(dismissal: &MatchArm, meaningful: &MatchArm) -> bool {
    if !is_trivial_expression(&dismissal.expression) {
        return false;
    }
    match (
        variant_name(&dismissal.pattern),
        variant_name(&meaningful.pattern),
    ) {
        (Some(dismissed), Some(kept)) => dismissed != kept,
        _ => false,
    }
}

fn variant_name(pattern: &Pattern) -> Option<&str> {
    match pattern {
        Pattern::EnumVariant { identifier, .. } => Some(unqualified_name(identifier)),
        _ => None,
    }
}

fn enum_has_extra_variants(subject_ty: &Type, store: &Store) -> bool {
    let Type::Nominal { id, .. } = subject_ty.strip_refs() else {
        return false;
    };
    store
        .variants_of(&id)
        .is_some_and(|variants| variants.len() > 2)
}