mirror of https://github.com/astral-sh/ruff
Add support for from __future__ import annotations (#189)
This commit is contained in:
parent
1dd3350a30
commit
b03a8728b5
|
|
@ -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)
|
||||||
|
|
@ -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];
|
||||||
|
|
|
||||||
|
|
@ -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(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue