mirror of https://github.com/astral-sh/ruff
[ty] Avoid enforcing standalone expression for tests in f-strings (#21967)
## Summary Based on what we do elsewhere and my understanding of "standalone" here... Closes https://github.com/astral-sh/ty/issues/1865.
This commit is contained in:
parent
8e13765b57
commit
682d29c256
|
|
@ -152,6 +152,20 @@ The expressions in these string annotations aren't valid expressions in this con
|
||||||
shouldn't panic.
|
shouldn't panic.
|
||||||
|
|
||||||
```py
|
```py
|
||||||
|
# Regression test for https://github.com/astral-sh/ty/issues/1865
|
||||||
|
# error: [fstring-type-annotation]
|
||||||
|
stringified_fstring_with_conditional: "f'{1 if 1 else 1}'"
|
||||||
|
# error: [fstring-type-annotation]
|
||||||
|
stringified_fstring_with_boolean_expression: "f'{1 or 2}'"
|
||||||
|
# error: [fstring-type-annotation]
|
||||||
|
stringified_fstring_with_generator_expression: "f'{(i for i in range(5))}'"
|
||||||
|
# error: [fstring-type-annotation]
|
||||||
|
stringified_fstring_with_list_comprehension: "f'{[i for i in range(5)]}'"
|
||||||
|
# error: [fstring-type-annotation]
|
||||||
|
stringified_fstring_with_dict_comprehension: "f'{ {i: i for i in range(5)} }'"
|
||||||
|
# error: [fstring-type-annotation]
|
||||||
|
stringified_fstring_with_set_comprehension: "f'{ {i for i in range(5)} }'"
|
||||||
|
|
||||||
a: "1 or 2"
|
a: "1 or 2"
|
||||||
b: "(x := 1)"
|
b: "(x := 1)"
|
||||||
# error: [invalid-type-form]
|
# error: [invalid-type-form]
|
||||||
|
|
|
||||||
|
|
@ -522,6 +522,11 @@ impl<'db> SemanticIndex<'db> {
|
||||||
self.scopes_by_node[&node.node_key()]
|
self.scopes_by_node[&node.node_key()]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the id of the scope that `node` creates, if it exists.
|
||||||
|
pub(crate) fn try_node_scope(&self, node: NodeWithScopeRef) -> Option<FileScopeId> {
|
||||||
|
self.scopes_by_node.get(&node.node_key()).copied()
|
||||||
|
}
|
||||||
|
|
||||||
/// Checks if there is an import of `__future__.annotations` in the global scope, which affects
|
/// Checks if there is an import of `__future__.annotations` in the global scope, which affects
|
||||||
/// the logic for type inference.
|
/// the logic for type inference.
|
||||||
pub(super) fn has_future_annotations(&self) -> bool {
|
pub(super) fn has_future_annotations(&self) -> bool {
|
||||||
|
|
|
||||||
|
|
@ -7926,7 +7926,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
let Some(first_comprehension) = comprehensions_iter.next() else {
|
let Some(first_comprehension) = comprehensions_iter.next() else {
|
||||||
unreachable!("Comprehension must contain at least one generator");
|
unreachable!("Comprehension must contain at least one generator");
|
||||||
};
|
};
|
||||||
self.infer_standalone_expression(&first_comprehension.iter, TypeContext::default());
|
self.infer_maybe_standalone_expression(&first_comprehension.iter, TypeContext::default());
|
||||||
|
|
||||||
if first_comprehension.is_async {
|
if first_comprehension.is_async {
|
||||||
EvaluationMode::Async
|
EvaluationMode::Async
|
||||||
|
|
@ -7946,9 +7946,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
|
|
||||||
let evaluation_mode = self.infer_first_comprehension_iter(generators);
|
let evaluation_mode = self.infer_first_comprehension_iter(generators);
|
||||||
|
|
||||||
let scope_id = self
|
let Some(scope_id) = self
|
||||||
.index
|
.index
|
||||||
.node_scope(NodeWithScopeRef::GeneratorExpression(generator));
|
.try_node_scope(NodeWithScopeRef::GeneratorExpression(generator))
|
||||||
|
else {
|
||||||
|
return Type::unknown();
|
||||||
|
};
|
||||||
let scope = scope_id.to_scope_id(self.db(), self.file());
|
let scope = scope_id.to_scope_id(self.db(), self.file());
|
||||||
let inference = infer_scope_types(self.db(), scope);
|
let inference = infer_scope_types(self.db(), scope);
|
||||||
let yield_type = inference.expression_type(elt.as_ref());
|
let yield_type = inference.expression_type(elt.as_ref());
|
||||||
|
|
@ -8021,9 +8024,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
|
|
||||||
self.infer_first_comprehension_iter(generators);
|
self.infer_first_comprehension_iter(generators);
|
||||||
|
|
||||||
let scope_id = self
|
let Some(scope_id) = self
|
||||||
.index
|
.index
|
||||||
.node_scope(NodeWithScopeRef::ListComprehension(listcomp));
|
.try_node_scope(NodeWithScopeRef::ListComprehension(listcomp))
|
||||||
|
else {
|
||||||
|
return Type::unknown();
|
||||||
|
};
|
||||||
let scope = scope_id.to_scope_id(self.db(), self.file());
|
let scope = scope_id.to_scope_id(self.db(), self.file());
|
||||||
let inference = infer_scope_types(self.db(), scope);
|
let inference = infer_scope_types(self.db(), scope);
|
||||||
let element_type = inference.expression_type(elt.as_ref());
|
let element_type = inference.expression_type(elt.as_ref());
|
||||||
|
|
@ -8046,9 +8052,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
|
|
||||||
self.infer_first_comprehension_iter(generators);
|
self.infer_first_comprehension_iter(generators);
|
||||||
|
|
||||||
let scope_id = self
|
let Some(scope_id) = self
|
||||||
.index
|
.index
|
||||||
.node_scope(NodeWithScopeRef::DictComprehension(dictcomp));
|
.try_node_scope(NodeWithScopeRef::DictComprehension(dictcomp))
|
||||||
|
else {
|
||||||
|
return Type::unknown();
|
||||||
|
};
|
||||||
let scope = scope_id.to_scope_id(self.db(), self.file());
|
let scope = scope_id.to_scope_id(self.db(), self.file());
|
||||||
let inference = infer_scope_types(self.db(), scope);
|
let inference = infer_scope_types(self.db(), scope);
|
||||||
let key_type = inference.expression_type(key.as_ref());
|
let key_type = inference.expression_type(key.as_ref());
|
||||||
|
|
@ -8071,9 +8080,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
|
|
||||||
self.infer_first_comprehension_iter(generators);
|
self.infer_first_comprehension_iter(generators);
|
||||||
|
|
||||||
let scope_id = self
|
let Some(scope_id) = self
|
||||||
.index
|
.index
|
||||||
.node_scope(NodeWithScopeRef::SetComprehension(setcomp));
|
.try_node_scope(NodeWithScopeRef::SetComprehension(setcomp))
|
||||||
|
else {
|
||||||
|
return Type::unknown();
|
||||||
|
};
|
||||||
let scope = scope_id.to_scope_id(self.db(), self.file());
|
let scope = scope_id.to_scope_id(self.db(), self.file());
|
||||||
let inference = infer_scope_types(self.db(), scope);
|
let inference = infer_scope_types(self.db(), scope);
|
||||||
let element_type = inference.expression_type(elt.as_ref());
|
let element_type = inference.expression_type(elt.as_ref());
|
||||||
|
|
@ -8165,14 +8177,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
builder.module(),
|
builder.module(),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
builder.infer_standalone_expression(iter, tcx)
|
builder.infer_maybe_standalone_expression(iter, tcx)
|
||||||
}
|
}
|
||||||
.iterate(builder.db())
|
.iterate(builder.db())
|
||||||
.homogeneous_element_type(builder.db())
|
.homogeneous_element_type(builder.db())
|
||||||
});
|
});
|
||||||
|
|
||||||
for expr in ifs {
|
for expr in ifs {
|
||||||
self.infer_standalone_expression(expr, TypeContext::default());
|
self.infer_maybe_standalone_expression(expr, TypeContext::default());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -8278,7 +8290,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
orelse,
|
orelse,
|
||||||
} = if_expression;
|
} = if_expression;
|
||||||
|
|
||||||
let test_ty = self.infer_standalone_expression(test, TypeContext::default());
|
let test_ty = self.infer_maybe_standalone_expression(test, TypeContext::default());
|
||||||
let body_ty = self.infer_expression(body, tcx);
|
let body_ty = self.infer_expression(body, tcx);
|
||||||
let orelse_ty = self.infer_expression(orelse, tcx);
|
let orelse_ty = self.infer_expression(orelse, tcx);
|
||||||
|
|
||||||
|
|
@ -10341,7 +10353,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
let ty = if index == values.len() - 1 {
|
let ty = if index == values.len() - 1 {
|
||||||
builder.infer_expression(value, TypeContext::default())
|
builder.infer_expression(value, TypeContext::default())
|
||||||
} else {
|
} else {
|
||||||
builder.infer_standalone_expression(value, TypeContext::default())
|
builder.infer_maybe_standalone_expression(value, TypeContext::default())
|
||||||
};
|
};
|
||||||
|
|
||||||
(ty, value.range())
|
(ty, value.range())
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue