mirror of
https://github.com/astral-sh/ruff.git
synced 2026-05-06 08:56:57 -04:00
[ty] remove static_expression_truthiness and improve reachability analysis (#22971)
## Summary cf: https://github.com/astral-sh/ruff/pull/19579, https://github.com/astral-sh/ruff/pull/20566#discussion_r2553196955 Currently, we use the query `static_expression_truthiness` to determine the truthiness of an expression. This always determines the truthinesses of non-definitely-bound places as `Ambiguous`, preventing cycles that occur when their reachabilities depend on themselves. However, this can lead to inaccurate analysis of code like the following. ```python def _(flag: bool): if flag: ALWAYS_TRUE_IF_BOUND = True # error: [possibly-unresolved-reference] "Name `ALWAYS_TRUE_IF_BOUND` used when possibly not defined" if ALWAYS_TRUE_IF_BOUND: x = 1 else: x = 2 # If `ALWAYS_TRUE_IF_BOUND` were not defined, an error would occur, and therefore the `x = 2` branch would never be executed. reveal_type(x) # expected: Literal[1], but: Literal[1, 2] ``` Since #20566, we can determine the `Place` by looking at the previous cycle result. Therefore, we can now remove `static_expression_truthiness`, improving reachability analysis of the above code. ## Test Plan mdtest updated
This commit is contained in:
committed by
GitHub
parent
9ebdd85d6d
commit
48a4d9d706
@@ -2708,8 +2708,9 @@ class Toggle:
|
||||
if check(self.y):
|
||||
self.y = True
|
||||
|
||||
reveal_type(Toggle().x) # revealed: Literal[True]
|
||||
reveal_type(Toggle().y) # revealed: Unknown | Literal[True]
|
||||
# Literal[True] or undefined
|
||||
reveal_type(Toggle().x) # revealed: Literal[True] | Unknown
|
||||
reveal_type(Toggle().y) # revealed: Unknown | Literal[True]
|
||||
```
|
||||
|
||||
Make sure that the growing union of literals `Literal[0, 1, 2, ...]` collapses to `int` during
|
||||
|
||||
@@ -1578,10 +1578,25 @@ def _(flag: bool):
|
||||
if True and ALWAYS_TRUE_IF_BOUND:
|
||||
x = 1
|
||||
|
||||
# error: [possibly-unresolved-reference] "Name `x` used when possibly not defined"
|
||||
# no error, x is considered definitely bound
|
||||
x
|
||||
```
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
if flag:
|
||||
ALWAYS_TRUE_IF_BOUND = True
|
||||
|
||||
# error: [possibly-unresolved-reference] "Name `ALWAYS_TRUE_IF_BOUND` used when possibly not defined"
|
||||
if True and ALWAYS_TRUE_IF_BOUND:
|
||||
x = 1
|
||||
else:
|
||||
x = 2
|
||||
|
||||
# If `ALWAYS_TRUE_IF_BOUND` were not defined, an error would occur, and therefore the `x = 2` branch would never be executed.
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
```
|
||||
|
||||
## Unreachable code
|
||||
|
||||
A closely related feature is the ability to detect unreachable code. For example, we do not emit a
|
||||
|
||||
@@ -209,7 +209,7 @@ use crate::semantic_index::predicate::{
|
||||
};
|
||||
use crate::types::{
|
||||
CallableTypes, IntersectionBuilder, Truthiness, Type, TypeContext, UnionBuilder, UnionType,
|
||||
infer_expression_type, static_expression_truthiness,
|
||||
infer_expression_type,
|
||||
};
|
||||
|
||||
/// A ternary formula that defines under what conditions a binding is visible. (A ternary formula
|
||||
@@ -864,7 +864,9 @@ impl ReachabilityConstraints {
|
||||
|
||||
match predicate.node {
|
||||
PredicateNode::Expression(test_expr) => {
|
||||
static_expression_truthiness(db, test_expr).negate_if(!predicate.is_positive)
|
||||
infer_expression_type(db, test_expr, TypeContext::default())
|
||||
.bool(db)
|
||||
.negate_if(!predicate.is_positive)
|
||||
}
|
||||
PredicateNode::ReturnsNever(CallableAndCallExpr {
|
||||
callable,
|
||||
|
||||
@@ -32,7 +32,7 @@ pub(crate) use self::diagnostic::register_lints;
|
||||
pub use self::diagnostic::{TypeCheckDiagnostics, UNDEFINED_REVEAL, UNRESOLVED_REFERENCE};
|
||||
pub(crate) use self::infer::{
|
||||
TypeContext, infer_complete_scope_types, infer_deferred_types, infer_definition_types,
|
||||
infer_expression_type, infer_expression_types, infer_scope_types, static_expression_truthiness,
|
||||
infer_expression_type, infer_expression_types, infer_scope_types,
|
||||
};
|
||||
pub use self::signatures::ParameterKind;
|
||||
pub(crate) use self::signatures::{CallableSignature, Signature};
|
||||
|
||||
@@ -53,8 +53,7 @@ use crate::types::function::FunctionType;
|
||||
use crate::types::generics::Specialization;
|
||||
use crate::types::unpacker::{UnpackResult, Unpacker};
|
||||
use crate::types::{
|
||||
ClassLiteral, KnownClass, StaticClassLiteral, Truthiness, Type, TypeAndQualifiers,
|
||||
declaration_type,
|
||||
ClassLiteral, KnownClass, StaticClassLiteral, Type, TypeAndQualifiers, declaration_type,
|
||||
};
|
||||
use crate::unpack::Unpack;
|
||||
use builder::TypeInferenceBuilder;
|
||||
@@ -428,30 +427,6 @@ impl<'db> TypeContext<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the statically-known truthiness of a given expression.
|
||||
///
|
||||
/// Returns [`Truthiness::Ambiguous`] in case any non-definitely bound places
|
||||
/// were encountered while inferring the type of the expression.
|
||||
#[salsa::tracked(
|
||||
cycle_initial=|_, _, _| Truthiness::Ambiguous,
|
||||
heap_size=get_size2::GetSize::get_heap_size
|
||||
)]
|
||||
pub(crate) fn static_expression_truthiness<'db>(
|
||||
db: &'db dyn Db,
|
||||
expression: Expression<'db>,
|
||||
) -> Truthiness {
|
||||
let inference = infer_expression_types_impl(db, InferExpression::Bare(expression));
|
||||
|
||||
if !inference.all_places_definitely_bound() {
|
||||
return Truthiness::Ambiguous;
|
||||
}
|
||||
|
||||
let file = expression.file(db);
|
||||
let module = parsed_module(db, file).load(db);
|
||||
let node = expression.node_ref(db, &module);
|
||||
inference.expression_type(node).bool(db)
|
||||
}
|
||||
|
||||
/// Infer the types for an [`Unpack`] operation.
|
||||
///
|
||||
/// This infers the expression type and performs structural match against the target expression
|
||||
@@ -905,12 +880,4 @@ impl<'db> ExpressionInference<'db> {
|
||||
fn fallback_type(&self) -> Option<Type<'db>> {
|
||||
self.extra.as_ref().and_then(|extra| extra.cycle_recovery)
|
||||
}
|
||||
|
||||
/// Returns true if all places in this expression are definitely bound.
|
||||
pub(crate) fn all_places_definitely_bound(&self) -> bool {
|
||||
self.extra
|
||||
.as_ref()
|
||||
.map(|e| e.all_definitely_bound)
|
||||
.unwrap_or(true)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user