Skip to content

Conversation

@Ruchir28
Copy link
Contributor

Summary

Resolves #21494

Treat function-scope annotated variables as locals per PEP 526 , preventing F821 false positives.

Test Plan

Added tests for these cases in crates/ruff_linter/resources/test/fixtures/pyflakes/F821_34.py

@astral-sh-bot
Copy link

astral-sh-bot bot commented Nov 20, 2025

ruff-ecosystem results

Linter (stable)

ℹ️ ecosystem check detected linter changes. (+1 -0 violations, +0 -0 fixes in 1 projects; 54 projects unchanged)

apache/airflow (+1 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --no-preview --select ALL

+ task-sdk/src/airflow/sdk/execution_time/callback_runner.py:109:28: RUF100 [*] Unused `noqa` directive (unused: `F821`)

Changes by rule (1 rules affected)

code total + violation - violation + fix - fix
RUF100 1 1 0 0 0

Linter (preview)

ℹ️ ecosystem check detected linter changes. (+1 -0 violations, +0 -0 fixes in 1 projects; 54 projects unchanged)

apache/airflow (+1 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview --select ALL

+ task-sdk/src/airflow/sdk/execution_time/callback_runner.py:109:28: RUF100 [*] Unused `noqa` directive (unused: `F821`)

Changes by rule (1 rules affected)

code total + violation - violation + fix - fix
RUF100 1 1 0 0 0

Comment on lines +6 to +8
def demonstrate_bare_local_annotation():
x: int
print(x)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still haven't looked too closely at this, but I agree that it's unfortunate not to flag this case. It might be worth looking into how ty handles this since it seems to get the expected result in both cases:

https://play.ty.dev/c6f45910-a2bd-4745-827b-f29a148f4e63

It doesn't seem to be related to nonlocal, though, because commenting it out doesn't change ty's diagnostics.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One option to do this is then something like

                    BindingKind::Annotation if !self.in_stub_file() => {
                        if self.scopes[self.bindings[binding_id].scope]
                            .kind
                            .is_function()
                        {
                            // If we're in a function scope, and the annotation is local to that scope,
                            // treat it as unbound (since it's not assigned).
                            // If it's an enclosing scope, we treat it as resolved (to support closures).
                            if index == 0 {
                                self.unresolved_references.push(
                                    name.range,
                                    self.exceptions(),
                                    UnresolvedReferenceFlags::empty(),
                                );
                                return ReadResult::UnboundLocal(binding_id);
                            }
                            
                            self.resolved_names.insert(name.into(), binding_id);
                            return ReadResult::Resolved(binding_id);
                        
                        }
                        continue;
                    }

if we do this then both the cases will be handled i.e. error in this case

def demonstrate_bare_local_annotation():
    x: int
    print(x)

and no error in the closure cases i.e. the setter,getter example

but then again we will have edge cases like these where no error will be reported [although even ty doesn't report them]

def demo_closure():
    x: int
    def func():
        return x

What do you think about this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

F821: Declared only variables

2 participants