From 847cbd0880827184c206dcdd229752bfca8143e4 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Mon, 29 Aug 2022 12:31:24 -0400 Subject: [PATCH] Fix forward annotations (#41) --- README.md | 9 ++++++--- resources/test/src/F401.py | 3 ++- src/check_ast.rs | 32 ++++++++++++++++++++++++++++++-- src/linter.rs | 5 +++-- src/visitor.rs | 11 +++++++---- 5 files changed, 48 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index a2055d2bd3..c196ec4489 100644 --- a/README.md +++ b/README.md @@ -125,10 +125,13 @@ ruff is distributed on [PyPI](https://pypi.org/project/ruff/), and published via For now, releases are cut and published manually: ```shell -for PYTHON_VERSION in 3.7 3.8 3.9 3.10 +for TARGET in x86_64-apple-darwin aarch64-apple-darwin do - maturin publish --username crmarsh --skip-existing --target x86_64-apple-darwin -i /usr/local/opt/python@${PYTHON_VERSION}/libexec/bin/python - maturin publish --username crmarsh --skip-existing --target aarch64-apple-darwin -i /usr/local/opt/python@${PYTHON_VERSION}/libexec/bin/python + maturin publish --username crmarsh --skip-existing --target ${TARGET} -i \ + /usr/local/opt/python@3.7/libexec/bin/python \ + /usr/local/opt/python@3.8/libexec/bin/python \ + /usr/local/opt/python@3.9/libexec/bin/python \ + /usr/local/opt/python@3.10/libexec/bin/python done ``` diff --git a/resources/test/src/F401.py b/resources/test/src/F401.py index 905547e64e..e646bed3f0 100644 --- a/resources/test/src/F401.py +++ b/resources/test/src/F401.py @@ -3,11 +3,12 @@ import functools from collections import ( Counter, OrderedDict, + namedtuple, ) class X: - def a(self): + def a(self) -> "namedtuple": x = os.environ["1"] y = Counter() return X diff --git a/src/check_ast.rs b/src/check_ast.rs index 08407a3993..ce5a89641f 100644 --- a/src/check_ast.rs +++ b/src/check_ast.rs @@ -1,8 +1,9 @@ use std::collections::{BTreeMap, BTreeSet}; use rustpython_parser::ast::{ - Arg, Arguments, Expr, ExprContext, ExprKind, Location, Stmt, StmtKind, Suite, + Arg, Arguments, Constant, Expr, ExprContext, ExprKind, Location, Stmt, StmtKind, Suite, }; +use rustpython_parser::parser; use crate::check_ast::ScopeKind::{Class, Function, Generator, Module}; use crate::checks::{Check, CheckCode, CheckKind}; @@ -45,7 +46,9 @@ struct Checker<'a> { checks: Vec, scopes: Vec, dead_scopes: Vec, + deferred: Vec, in_f_string: bool, + in_annotation: bool, } impl Checker<'_> { @@ -55,7 +58,9 @@ impl Checker<'_> { checks: vec![], scopes: vec![], dead_scopes: vec![], + deferred: vec![], in_f_string: false, + in_annotation: false, } } } @@ -252,6 +257,13 @@ impl Visitor for Checker<'_> { } } + fn visit_annotation(&mut self, expr: &Expr) { + let initial = self.in_annotation; + self.in_annotation = true; + self.visit_expr(expr); + self.in_annotation = initial; + } + fn visit_expr(&mut self, expr: &Expr) { let initial = self.in_f_string; match &expr.node { @@ -285,6 +297,10 @@ impl Visitor for Checker<'_> { } self.in_f_string = true; } + ExprKind::Constant { + value: Constant::Str(value), + .. + } if self.in_annotation => self.deferred.push(value.to_string()), _ => {} }; @@ -365,6 +381,7 @@ impl Checker<'_> { fn add_binding(&mut self, binding: Binding) { // TODO(charlie): Don't treat annotations as assignments if there is an existing value. let scope = self.scopes.last_mut().expect("No current scope found."); + scope.values.insert( binding.name.clone(), match scope.values.get(&binding.name) { @@ -408,6 +425,14 @@ impl Checker<'_> { } } + fn check_deferred(&mut self, path: &str) { + for value in self.deferred.clone() { + if let Ok(expr) = &parser::parse_expression(&value, path) { + self.visit_expr(expr); + } + } + } + fn check_dead_scopes(&mut self) { if self.settings.select.contains(&CheckCode::F401) { // TODO(charlie): Handle `__all__`. @@ -427,15 +452,18 @@ impl Checker<'_> { } } -pub fn check_ast(python_ast: &Suite, settings: &Settings) -> Vec { +pub fn check_ast(python_ast: &Suite, settings: &Settings, path: &str) -> Vec { let mut checker = Checker::new(settings); checker.push_scope(Scope { kind: Module, values: BTreeMap::new(), }); + for stmt in python_ast { checker.visit_stmt(stmt); } + checker.check_deferred(path); + checker.pop_scope(); checker.check_dead_scopes(); checker.checks diff --git a/src/linter.rs b/src/linter.rs index 57bc322331..6539657d77 100644 --- a/src/linter.rs +++ b/src/linter.rs @@ -30,8 +30,9 @@ pub fn check_path(path: &Path, settings: &Settings, mode: &cache::Mode) -> Resul .iter() .any(|check_code| matches!(check_code.lint_source(), LintSource::AST)) { - let python_ast = parser::parse_program(&contents, &path.to_string_lossy())?; - checks.extend(check_ast(&python_ast, settings)); + let path = path.to_string_lossy(); + let python_ast = parser::parse_program(&contents, &path)?; + checks.extend(check_ast(&python_ast, settings, &path)); } // Run the lines-based checks. diff --git a/src/visitor.rs b/src/visitor.rs index 968bcf5f8d..3ca6a9f877 100644 --- a/src/visitor.rs +++ b/src/visitor.rs @@ -8,6 +8,9 @@ pub trait Visitor { fn visit_stmt(&mut self, stmt: &Stmt) { walk_stmt(self, stmt); } + fn visit_annotation(&mut self, expr: &Expr) { + walk_expr(self, expr); + } fn visit_expr(&mut self, expr: &Expr) { walk_expr(self, expr); } @@ -80,7 +83,7 @@ pub fn walk_stmt(visitor: &mut V, stmt: &Stmt) { visitor.visit_expr(expr) } for expr in returns { - visitor.visit_expr(expr); + visitor.visit_annotation(expr); } } StmtKind::AsyncFunctionDef { @@ -100,7 +103,7 @@ pub fn walk_stmt(visitor: &mut V, stmt: &Stmt) { visitor.visit_expr(expr) } for expr in returns { - visitor.visit_expr(expr); + visitor.visit_annotation(expr); } } StmtKind::ClassDef { @@ -152,7 +155,7 @@ pub fn walk_stmt(visitor: &mut V, stmt: &Stmt) { .. } => { visitor.visit_expr(target); - visitor.visit_expr(annotation); + visitor.visit_annotation(annotation); if let Some(expr) = value { visitor.visit_expr(expr) } @@ -513,7 +516,7 @@ pub fn walk_arguments(visitor: &mut V, arguments: &Argument pub fn walk_arg(visitor: &mut V, arg: &Arg) { if let Some(expr) = &arg.node.annotation { - visitor.visit_expr(expr) + visitor.visit_annotation(expr) } }