Add support for from __future__ import annotations (#189)

This commit is contained in:
Charlie Marsh 2022-09-14 18:22:19 -04:00 committed by GitHub
parent 1dd3350a30
commit b03a8728b5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 93 additions and 5 deletions

View File

@ -0,0 +1,27 @@
from __future__ import annotations
from dataclasses import dataclass
from models import Fruit, Nut
@dataclass
class Foo:
x: int
y: int
@classmethod
def a(cls) -> Foo:
return cls(x=0, y=0)
@classmethod
def b(cls) -> "Foo":
return cls(x=0, y=0)
@classmethod
def c(cls) -> Bar:
return cls(x=0, y=0)
@classmethod
def d(cls) -> Fruit:
return cls(x=0, y=0)

View File

@ -36,7 +36,8 @@ struct Checker<'a> {
scopes: Vec<Scope>, scopes: Vec<Scope>,
scope_stack: Vec<usize>, scope_stack: Vec<usize>,
dead_scopes: Vec<usize>, dead_scopes: Vec<usize>,
deferred_annotations: Vec<(Location, &'a str)>, deferred_string_annotations: Vec<(Location, &'a str)>,
deferred_annotations: Vec<(&'a Expr, Vec<usize>, Vec<usize>)>,
deferred_functions: Vec<(&'a Stmt, Vec<usize>, Vec<usize>)>, deferred_functions: Vec<(&'a Stmt, Vec<usize>, Vec<usize>)>,
deferred_lambdas: Vec<(&'a Expr, Vec<usize>, Vec<usize>)>, deferred_lambdas: Vec<(&'a Expr, Vec<usize>, Vec<usize>)>,
deferred_assignments: Vec<usize>, deferred_assignments: Vec<usize>,
@ -47,6 +48,7 @@ struct Checker<'a> {
seen_non_import: bool, seen_non_import: bool,
seen_docstring: bool, seen_docstring: bool,
futures_allowed: bool, futures_allowed: bool,
annotations_future_enabled: bool,
} }
impl<'a> Checker<'a> { impl<'a> Checker<'a> {
@ -67,6 +69,7 @@ impl<'a> Checker<'a> {
scopes: vec![], scopes: vec![],
scope_stack: vec![], scope_stack: vec![],
dead_scopes: vec![], dead_scopes: vec![],
deferred_string_annotations: vec![],
deferred_annotations: vec![], deferred_annotations: vec![],
deferred_functions: vec![], deferred_functions: vec![],
deferred_lambdas: vec![], deferred_lambdas: vec![],
@ -77,6 +80,7 @@ impl<'a> Checker<'a> {
seen_non_import: false, seen_non_import: false,
seen_docstring: false, seen_docstring: false,
futures_allowed: true, futures_allowed: true,
annotations_future_enabled: false,
} }
} }
} }
@ -423,6 +427,10 @@ where
}, },
); );
if alias.node.name == "annotations" {
self.annotations_future_enabled = true;
}
if self.settings.select.contains(&CheckCode::F407) if self.settings.select.contains(&CheckCode::F407)
&& !ALL_FEATURE_NAMES.contains(&alias.node.name.deref()) && !ALL_FEATURE_NAMES.contains(&alias.node.name.deref())
{ {
@ -581,6 +589,17 @@ where
let prev_in_literal = self.in_literal; let prev_in_literal = self.in_literal;
let prev_in_annotation = self.in_annotation; let prev_in_annotation = self.in_annotation;
// Important:
if self.in_annotation && self.annotations_future_enabled {
self.deferred_annotations.push((
expr,
self.scope_stack.clone(),
self.parent_stack.clone(),
));
visitor::walk_expr(self, expr);
return;
}
// Pre-visit. // Pre-visit.
match &expr.node { match &expr.node {
ExprKind::Subscript { value, .. } => { ExprKind::Subscript { value, .. } => {
@ -723,7 +742,8 @@ where
value: Constant::Str(value), value: Constant::Str(value),
.. ..
} if self.in_annotation && !self.in_literal => { } if self.in_annotation && !self.in_literal => {
self.deferred_annotations.push((expr.location, value)); self.deferred_string_annotations
.push((expr.location, value));
} }
ExprKind::GeneratorExp { .. } ExprKind::GeneratorExp { .. }
| ExprKind::ListComp { .. } | ExprKind::ListComp { .. }
@ -1195,11 +1215,19 @@ impl<'a> Checker<'a> {
} }
} }
fn check_deferred_annotations<'b>(&mut self, path: &str, allocator: &'b mut Vec<Expr>) fn check_deferred_annotations(&mut self) {
while let Some((expr, scopes, parents)) = self.deferred_annotations.pop() {
self.parent_stack = parents;
self.scope_stack = scopes;
self.visit_expr(expr);
}
}
fn check_deferred_string_annotations<'b>(&mut self, path: &str, allocator: &'b mut Vec<Expr>)
where where
'b: 'a, 'b: 'a,
{ {
while let Some((location, expression)) = self.deferred_annotations.pop() { while let Some((location, expression)) = self.deferred_string_annotations.pop() {
if let Ok(mut expr) = parser::parse_expression(expression, path) { if let Ok(mut expr) = parser::parse_expression(expression, path) {
relocate_expr(&mut expr, location); relocate_expr(&mut expr, location);
allocator.push(expr); allocator.push(expr);
@ -1344,8 +1372,9 @@ pub fn check_ast(
checker.check_deferred_functions(); checker.check_deferred_functions();
checker.check_deferred_lambdas(); checker.check_deferred_lambdas();
checker.check_deferred_assignments(); checker.check_deferred_assignments();
checker.check_deferred_annotations();
let mut allocator = vec![]; let mut allocator = vec![];
checker.check_deferred_annotations(path, &mut allocator); checker.check_deferred_string_annotations(path, &mut allocator);
// Reset the scope to module-level, and check all consumed scopes. // Reset the scope to module-level, and check all consumed scopes.
checker.scope_stack = vec![GLOBAL_SCOPE_INDEX]; checker.scope_stack = vec![GLOBAL_SCOPE_INDEX];

View File

@ -1599,4 +1599,36 @@ mod tests {
Ok(()) Ok(())
} }
#[test]
fn future_annotations() -> Result<()> {
let mut actual = check_path(
Path::new("./resources/test/fixtures/future_annotations.py"),
&settings::Settings {
line_length: 88,
exclude: vec![],
select: BTreeSet::from([CheckCode::F401, CheckCode::F821]),
},
&fixer::Mode::Generate,
)?;
actual.sort_by_key(|check| check.location);
let expected = vec![
Check {
kind: CheckKind::UnusedImport("models.Nut".to_string()),
location: Location::new(5, 1),
fix: None,
},
Check {
kind: CheckKind::UndefinedName("Bar".to_string()),
location: Location::new(22, 19),
fix: None,
},
];
assert_eq!(actual.len(), expected.len());
for i in 0..actual.len() {
assert_eq!(actual[i], expected[i]);
}
Ok(())
}
} }