Repository: astral-sh/ruff
File: crates/ty_python_semantic/src/place.rs (Rust)

Code before:
    };

    let scope = definition.scope(db);
    let use_def = use_def_map(db, scope);
    let loop_header = get_loop_header(db, loop_header_definition.loop_token());
    let place = loop_header_definition.place();

    let mut has_defined_bindings = false;
    let mut deleted_reachability = Truthiness::AlwaysFalse;
    let mut reachable_bindings = FxIndexSet::default();

    for live_binding in loop_header.bindings_for_place(place) {
        let reachability = if initial {
            Truthiness::Ambiguous
        } else {
            use_def.evaluate_reachability(db, live_binding.reachability_constraint)
        };
        // Skip unreachable bindings.
        if reachability.is_always_false() {
            continue;
        }

        match use_def.definition(live_binding.binding) {
            DefinitionState::Defined(def) => {
                has_defined_bindings = true;
                if def != definition {
                    reachable_bindings.insert(ReachableLoopBinding {
                        definition: def,
                        narrowing_constraint: live_binding.narrowing_constraint,
                    });
                }
            }
            // `del` in the loop body is always visible to code after the loop via the
            // normal control flow merge. Updating `deleted_reachability` here is
            // necessary for prior uses in the loop to see it.
            DefinitionState::Deleted => {
                deleted_reachability = deleted_reachability.or(reachability);
            }
            // If UNBOUND is visible at loop-back, then it was visible before the loop.
            // Loop header definitions don't shadow preexisting bindings, so we don't
            // need to do anything with this.
            DefinitionState::Undefined => {}
        }
    }

    LoopHeaderReachability {
        has_defined_bindings,
        deleted_reachability,
        reachable_bindings,
    }
}

Review comment (performance):
It's gross that you need to do this check, and it's my fault 😬 It really makes no sense for loop headers to include bindings from before the loop. I don't know why this didn't occur to me before, but it's actually relatively simple to filter them out. I've opened https://github.com/astral-sh/ruff/pull/23536 to see whether there are any ecosystem consequences to doing that. It seems to be a ~10% performance improvement in `isort` too. I wonder if that will compound with this PR?

I think it's fine to land this as-is, and then I can rebase https://github.com/astral-sh/ruff/pull/23536 on top of it and remove this check there?

Code after:
    };

    let scope = definition.scope(db);
    let use_def = use_def_map(db, scope);
    let loop_header = get_loop_header(db, loop_header_definition.loop_token());
    let place = loop_header_definition.place();

    let mut has_defined_bindings = false;
    let mut deleted_reachability = Truthiness::AlwaysFalse;
    let mut reachable_bindings = FxIndexSet::default();

    for live_binding in loop_header.bindings_for_place(place) {
        let reachability = if is_cycle_initial {
            Truthiness::Ambiguous
        } else {
            use_def.evaluate_reachability(db, live_binding.reachability_constraint)
        };
        // Skip unreachable bindings.
        if reachability.is_always_false() {
            continue;
        }

        match use_def.definition(live_binding.binding) {
            DefinitionState::Defined(def) => {
                has_defined_bindings = true;
                if def != definition {
                    reachable_bindings.insert(ReachableLoopBinding {
                        definition: def,
                        narrowing_constraint: live_binding.narrowing_constraint,
                    });
                }
            }
            // `del` in the loop body is always visible to code after the loop via the
            // normal control flow merge. Updating `deleted_reachability` here is
            // necessary for prior uses in the loop to see it.
            DefinitionState::Deleted => {
                deleted_reachability = deleted_reachability.or(reachability);
            }
            // If UNBOUND is visible at loop-back, then it was visible before the loop.
            // Loop header definitions don't shadow preexisting bindings, so we don't
            // need to do anything with this.
            DefinitionState::Undefined => {}
        }
    }

    LoopHeaderReachability {
        has_defined_bindings,
        deleted_reachability,
        reachable_bindings,
    }
}