lisette-semantics 0.2.17

Little language inspired by Rust that compiles to Go
Documentation
use diagnostics::LisetteDiagnostic;
use syntax::ast::{Expression, MatchArm, MatchOrigin, Pattern, Span};
use syntax::types::unqualified_name;

use super::helpers::{has_escaping_control_flow, is_side_effect_free};

pub fn check_manual_unwrap_or(expression: &Expression, diagnostics: &mut Vec<LisetteDiagnostic>) {
    let Expression::Match {
        subject,
        arms,
        origin,
        span,
        ..
    } = expression
    else {
        return;
    };

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

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

    let subject_ty = subject.get_type();
    let (success_variant, failure_variant) = if subject_ty.is_option() {
        ("Some", "None")
    } else if subject_ty.is_result() {
        ("Ok", "Err")
    } else {
        return;
    };

    let default = if is_success_arm(&arms[0], success_variant) {
        failure_default(&arms[1], failure_variant)
    } else if is_success_arm(&arms[1], success_variant) {
        failure_default(&arms[0], failure_variant)
    } else {
        return;
    };

    let Some(default) = default else {
        return;
    };

    if default.diverges().is_some() {
        return;
    }

    let default_has_effects = !is_side_effect_free(default);
    if default_has_effects && has_escaping_control_flow(default) {
        return;
    }

    let match_keyword_span = Span::new(span.file_id, span.byte_offset, 5);
    diagnostics.push(diagnostics::lint::manual_unwrap_or(
        &match_keyword_span,
        default_has_effects,
    ));
}

fn enum_variant_fields<'a>(arm: &'a MatchArm, variant: &str) -> Option<&'a [Pattern]> {
    if arm.has_guard() {
        return None;
    }
    let Pattern::EnumVariant {
        identifier, fields, ..
    } = &arm.pattern
    else {
        return None;
    };
    if unqualified_name(identifier) != variant {
        return None;
    }
    Some(fields)
}

fn is_success_arm(arm: &MatchArm, success_variant: &str) -> bool {
    let Some(fields) = enum_variant_fields(arm, success_variant) else {
        return false;
    };
    let [
        Pattern::Identifier {
            identifier: bound, ..
        },
    ] = fields
    else {
        return false;
    };
    let Expression::Identifier { value, .. } = arm.expression.unwrap_parens() else {
        return false;
    };
    value == bound
}

fn failure_default<'a>(arm: &'a MatchArm, failure_variant: &str) -> Option<&'a Expression> {
    let fields = enum_variant_fields(arm, failure_variant)?;
    if !fields
        .iter()
        .all(|field| matches!(field, Pattern::WildCard { .. }))
    {
        return None;
    }
    Some(arm.expression.unwrap_parens())
}