mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-29 11:07:54 +00:00
[ty] Correctly handle calls to functions marked as returning Never / NoReturn (#18333)
## Summary `ty` does not understand that calls to functions which have been annotated as having a return type of `Never` / `NoReturn` are terminal. This PR fixes that, by adding new reachability constraints when call expressions are seen. If the call expression evaluates to `Never`, the code following it will be considered to be unreachable. Note that, for adding these constraints, we only consider call expressions at the statement level, and that too only inside function scopes. This is because otherwise, the number of such constraints becomes too high, and evaluating them later on during type inference results in a major performance degradation. Fixes https://github.com/astral-sh/ty/issues/180 ## Test Plan New mdtests. ## Ecosystem changes This PR removes the following false-positives: - "Function can implicitly return `None`, which is not assignable to ...". - "Name `foo` used when possibly not defind" - because the branch in which it is not defined has a `NoReturn` call, or when `foo` was imported in a `try`, and the except had a `NoReturn` call. --------- Co-authored-by: David Peter <mail@david-peter.de>
This commit is contained in:
parent
a33cff2b12
commit
f4bd74ab6a
10 changed files with 437 additions and 72 deletions
|
|
@ -204,7 +204,8 @@ use crate::place::{RequiresExplicitReExport, imported_symbol};
|
|||
use crate::semantic_index::expression::Expression;
|
||||
use crate::semantic_index::place_table;
|
||||
use crate::semantic_index::predicate::{
|
||||
PatternPredicate, PatternPredicateKind, Predicate, PredicateNode, Predicates, ScopedPredicateId,
|
||||
CallableAndCallExpr, PatternPredicate, PatternPredicateKind, Predicate, PredicateNode,
|
||||
Predicates, ScopedPredicateId,
|
||||
};
|
||||
use crate::types::{Truthiness, Type, infer_expression_type};
|
||||
|
||||
|
|
@ -684,6 +685,53 @@ impl ReachabilityConstraints {
|
|||
let ty = infer_expression_type(db, test_expr);
|
||||
ty.bool(db).negate_if(!predicate.is_positive)
|
||||
}
|
||||
PredicateNode::ReturnsNever(CallableAndCallExpr {
|
||||
callable,
|
||||
call_expr,
|
||||
}) => {
|
||||
// We first infer just the type of the callable. In the most likely case that the
|
||||
// function is not marked with `NoReturn`, or that it always returns `NoReturn`,
|
||||
// doing so allows us to avoid the more expensive work of inferring the entire call
|
||||
// expression (which could involve inferring argument types to possibly run the overload
|
||||
// selection algorithm).
|
||||
// Avoiding this on the happy-path is important because these constraints can be
|
||||
// very large in number, since we add them on all statement level function calls.
|
||||
let ty = infer_expression_type(db, callable);
|
||||
|
||||
let overloads_iterator =
|
||||
if let Some(Type::Callable(callable)) = ty.into_callable(db) {
|
||||
callable.signatures(db).overloads.iter()
|
||||
} else {
|
||||
return Truthiness::AlwaysFalse.negate_if(!predicate.is_positive);
|
||||
};
|
||||
|
||||
let (no_overloads_return_never, all_overloads_return_never) = overloads_iterator
|
||||
.fold((true, true), |(none, all), overload| {
|
||||
let overload_returns_never =
|
||||
overload.return_ty.is_some_and(|return_type| {
|
||||
return_type.is_equivalent_to(db, Type::Never)
|
||||
});
|
||||
|
||||
(
|
||||
none && !overload_returns_never,
|
||||
all && overload_returns_never,
|
||||
)
|
||||
});
|
||||
|
||||
if no_overloads_return_never {
|
||||
Truthiness::AlwaysFalse
|
||||
} else if all_overloads_return_never {
|
||||
Truthiness::AlwaysTrue
|
||||
} else {
|
||||
let call_expr_ty = infer_expression_type(db, call_expr);
|
||||
if call_expr_ty.is_equivalent_to(db, Type::Never) {
|
||||
Truthiness::AlwaysTrue
|
||||
} else {
|
||||
Truthiness::AlwaysFalse
|
||||
}
|
||||
}
|
||||
.negate_if(!predicate.is_positive)
|
||||
}
|
||||
PredicateNode::Pattern(inner) => Self::analyze_single_pattern_predicate(db, inner),
|
||||
PredicateNode::StarImportPlaceholder(star_import) => {
|
||||
let place_table = place_table(db, star_import.scope(db));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue