Repository: astral-sh/ruff
File: crates/ruff_linter/src/rules/pylint/rules/swap_with_temporary_variable.rs (Rust)

Code before:
    first_var: &'a Name,
    second_var: &'a Name,
    is_temp_var_reused: bool,
}

impl Violation for SwapWithTemporaryVariable<'_> {
    const FIX_AVAILABILITY: crate::FixAvailability = crate::FixAvailability::Sometimes;

    #[derive_message_formats]
    fn message(&self) -> String {
        let SwapWithTemporaryVariable {
            first_var,
            second_var,
            is_temp_var_reused: _,
        } = self;
        format!("Consider swapping `{first_var}` and `{second_var}` by using tuple unpacking")
    }

    fn fix_title(&self) -> Option<String> {
        let SwapWithTemporaryVariable {
            first_var,
            second_var,
            is_temp_var_reused,
        } = self;

        if !is_temp_var_reused {
            Some(format!(
                "Use `{first_var}, {second_var} = {second_var}, {first_var}` instead"
            ))
        } else {
            None
        }
    }
}

pub(crate) fn swap_with_temporary_variable(checker: &Checker, definition: &Definition) {
    let consecutive_assignments = match_consecutive_assignments(definition);

    for stmt_sequence in consecutive_assignments.tuple_windows() {
        let (Some(stmt_a), Some(stmt_b), Some(stmt_c)) = stmt_sequence else {
            continue;
        };

        // Detect patterns like:
        // temp = x
        // x = y
        // y = temp
        if stmt_a.value == stmt_b.target
            && stmt_b.value == stmt_c.target
            && stmt_a.target == stmt_c.value
        {

Review comment (suggestion):
I think if the variable is reused, we shouldn't offer a diagnostic at all right? That seems like an indicator that the user wants the temporary variable instead of the tuple swap, if I'm following correctly.

Code after:
}

impl Violation for SwapWithTemporaryVariable<'_> {
    const FIX_AVAILABILITY: crate::FixAvailability = crate::FixAvailability::Sometimes;

    #[derive_message_formats]
    fn message(&self) -> String {
        "Unnecessary temporary variable".to_string()
    }

    fn fix_title(&self) -> Option<String> {
        let SwapWithTemporaryVariable { first, second } = self;

        Some(format!(
            "Use `{first}, {second} = {second}, {first}` instead"
        ))
    }
}

pub(crate) fn swap_with_temporary_variable(checker: &Checker, scope_id: ScopeId, scope: &Scope) {
    let consecutive_assignments = scope.binding_ids().filter_map(|binding_id| {
        match_consecutive_assignments(checker.semantic(), scope_id, binding_id)
    });

    for (stmt_a, stmt_b, stmt_c) in consecutive_assignments {
        // Detect patterns like:
        // temp = x
        // x = y
        // y = temp
        if stmt_a.value == stmt_b.target
            && stmt_b.value == stmt_c.target
            && stmt_a.target == stmt_c.value
        {
            // check whether there is any later read reference to the temporary variable -
            // in this case the automatic hotfix would result in broken code, because
            // this later read would attempt to read from a variable that no longer exists
            let is_variable_reused_later = is_variable_read_after(checker, &stmt_a);
            if is_variable_reused_later {
                continue;
            }

            let first = stmt_b.target;
            let second = stmt_c.target;
            let edit_range = TextRange::new(stmt_a.start(), stmt_c.end());
            let edit = Edit::range_replacement(
                format!("{first}, {second} = {second}, {first}"),
                edit_range,
            );

            // The quick fix would remove comments, hence it's unsafe if there are any comments in the relevant code part.
            let applicability = if checker.comment_ranges().intersects(edit.range()) {