mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 13:51:37 +00:00
[red-knot] AnnAssign with no RHS is not a Definition (#13247)
My plan for handling declared types is to introduce a `Declaration` in addition to `Definition`. A `Declaration` is an annotation of a name with a type; a `Definition` is an actual runtime assignment of a value to a name. A few things (an annotated function parameter, an annotated-assignment with an RHS) are both a `Definition` and a `Declaration`. This more cleanly separates type inference (only cares about `Definition`) from declared types (only impacted by a `Declaration`), and I think it will work out better than trying to squeeze everything into `Definition`. One of the tests in this PR (`annotation_only_assignment_transparent_to_local_inference`) demonstrates one reason why. The statement `x: int` should have no effect on local inference of the type of `x`; whatever the locally inferred type of `x` was before `x: int` should still be the inferred type after `x: int`. This is actually quite hard to do if `x: int` is considered a `Definition`, because a core assumption of the use-def map is that a `Definition` replaces the previous value. To achieve this would require some hackery to effectively treat `x: int` sort of as if it were `x: int = x`, but it's not really even equivalent to that, so this approach gets quite ugly. As a first step in this plan, this PR stops treating AnnAssign with no RHS as a `Definition`, which fixes behavior in a couple added tests. This actually makes things temporarily worse for the ellipsis-type test, since it is defined in typeshed only using annotated assignments with no RHS. This will be fixed properly by the upcoming addition of declarations, which should also treat a declared type as sufficient to import a name, at least from a stub.
This commit is contained in:
parent
65cc6ec41d
commit
2a3775e525
3 changed files with 68 additions and 25 deletions
|
@ -498,7 +498,6 @@ where
|
|||
}
|
||||
ast::Stmt::AnnAssign(node) => {
|
||||
debug_assert!(self.current_assignment.is_none());
|
||||
// TODO deferred annotation visiting
|
||||
self.visit_expr(&node.annotation);
|
||||
if let Some(value) = &node.value {
|
||||
self.visit_expr(value);
|
||||
|
@ -633,21 +632,22 @@ where
|
|||
|
||||
match expr {
|
||||
ast::Expr::Name(name_node @ ast::ExprName { id, ctx, .. }) => {
|
||||
let mut flags = match ctx {
|
||||
ast::ExprContext::Load => SymbolFlags::IS_USED,
|
||||
ast::ExprContext::Store => SymbolFlags::IS_DEFINED,
|
||||
ast::ExprContext::Del => SymbolFlags::IS_DEFINED,
|
||||
ast::ExprContext::Invalid => SymbolFlags::empty(),
|
||||
let flags = match (ctx, self.current_assignment) {
|
||||
(ast::ExprContext::Store, Some(CurrentAssignment::AugAssign(_))) => {
|
||||
// For augmented assignment, the target expression is also used.
|
||||
SymbolFlags::IS_DEFINED | SymbolFlags::IS_USED
|
||||
}
|
||||
(ast::ExprContext::Store, Some(CurrentAssignment::AnnAssign(ann_assign)))
|
||||
if ann_assign.value.is_none() =>
|
||||
{
|
||||
// An annotated assignment that doesn't assign a value is not a Definition
|
||||
SymbolFlags::empty()
|
||||
}
|
||||
(ast::ExprContext::Load, _) => SymbolFlags::IS_USED,
|
||||
(ast::ExprContext::Store, _) => SymbolFlags::IS_DEFINED,
|
||||
(ast::ExprContext::Del, _) => SymbolFlags::IS_DEFINED,
|
||||
(ast::ExprContext::Invalid, _) => SymbolFlags::empty(),
|
||||
};
|
||||
if matches!(
|
||||
self.current_assignment,
|
||||
Some(CurrentAssignment::AugAssign(_))
|
||||
) && !ctx.is_invalid()
|
||||
{
|
||||
// For augmented assignment, the target expression is also used, so we should
|
||||
// record that as a use.
|
||||
flags |= SymbolFlags::IS_USED;
|
||||
}
|
||||
let symbol = self.add_or_update_symbol(id.clone(), flags);
|
||||
if flags.contains(SymbolFlags::IS_DEFINED) {
|
||||
match self.current_assignment {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue