mirror of
https://github.com/astral-sh/ruff.git
synced 2026-10-28 10:45:37 +08:00
[ty] Skip predicates that cannot affect narrowing (#29736)
## Summary
A full isort check went from 218 ms to 184 ms in the ecosystem run, a
16% reduction. Across all 163 projects the average timing factor was
1.00x, and no diagnostics changed.
In this example, `ready` can be true for either type of `value`, so the
path that skips `isinstance` already accounts for both possibilities.
```python
from typing import reveal_type
def f(value: str | None, ready: bool) -> None:
if ready or isinstance(value, str):
reveal_type(value) # str | None
```
| isort | Before | After | Wall time saved | CPU time saved | Runs per
build |
| --- | ---: | ---: | ---: | ---: | ---: |
| This patch alone | 336.0 ms | 296.9 ms | 11.6% | 6.8% | 20 |
| With #29701 and #29706 | 227.1 ms | 215.6 ms | 5.1% | 2.9% | 60 |
This commit is contained in:
1 parent
9525a0c4e8
commit
d20ca8e9bf
1 file changed
+27
-13
@@ -1421,7 +1421,7 @@ impl<'a, 'db> NarrowingProjector<'a, 'db> {
|
||||
type Id = ScopedNarrowingConstraint;
|
||||
enum Action {
|
||||
Visit(Id),
|
||||
AnalyzeNonTerminal(Id),
|
||||
VisitBranches(Id),
|
||||
FinishNonTerminal { id: Id, branch: Id },
|
||||
FinishPredicate(Id),
|
||||
}
|
||||
@@ -1482,26 +1482,32 @@ impl<'a, 'db> NarrowingProjector<'a, 'db> {
|
||||
self.project_cache.insert((id, self.base_ty), projected);
|
||||
continue;
|
||||
}
|
||||
let is_control_flow_gate = matches!(
|
||||
actions.push(Action::VisitBranches(id));
|
||||
actions.push(Action::Visit(node.if_uncertain));
|
||||
}
|
||||
Action::VisitBranches(id) => {
|
||||
let node = self.constraints.get_interior_node(id);
|
||||
// This path bypasses the predicate. If it admits every value, the
|
||||
// conditional paths cannot narrow this place.
|
||||
if self.projected_node(node.if_uncertain)
|
||||
== ProjectedNarrowingNodeId::ALWAYS_TRUE
|
||||
{
|
||||
self.project_cache
|
||||
.insert((id, self.base_ty), ProjectedNarrowingNodeId::ALWAYS_TRUE);
|
||||
continue;
|
||||
}
|
||||
let predicate = self.predicates[node.atom];
|
||||
if !matches!(
|
||||
predicate.node,
|
||||
PredicateNode::IsNonTerminalCall(_)
|
||||
| PredicateNode::ContextManagerSuppresses { .. }
|
||||
| PredicateNode::FinallyNormalPathImpossible { .. }
|
||||
);
|
||||
|
||||
if is_control_flow_gate {
|
||||
actions.push(Action::AnalyzeNonTerminal(id));
|
||||
actions.push(Action::Visit(node.if_uncertain));
|
||||
} else {
|
||||
) {
|
||||
actions.push(Action::FinishPredicate(id));
|
||||
actions.push(Action::Visit(node.if_false));
|
||||
actions.push(Action::Visit(node.if_uncertain));
|
||||
actions.push(Action::Visit(node.if_true));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Action::AnalyzeNonTerminal(id) => {
|
||||
let node = self.constraints.get_interior_node(id);
|
||||
let predicate = self.predicates[node.atom];
|
||||
let branch = match analyze_single(db, self.env, &predicate) {
|
||||
Truthiness::AlwaysTrue => node.if_true,
|
||||
Truthiness::AlwaysFalse => node.if_false,
|
||||
@@ -1528,6 +1534,14 @@ impl<'a, 'db> NarrowingProjector<'a, 'db> {
|
||||
let if_true = self.projected_node(node.if_true);
|
||||
let if_uncertain = self.projected_node(node.if_uncertain);
|
||||
let if_false = self.projected_node(node.if_false);
|
||||
// If the bypass already covers both conditional branches, their
|
||||
// predicate cannot affect the projection.
|
||||
if [if_true, if_false].into_iter().all(|branch| {
|
||||
branch == ProjectedNarrowingNodeId::ALWAYS_FALSE || branch == if_uncertain
|
||||
}) {
|
||||
self.project_cache.insert((id, self.base_ty), if_uncertain);
|
||||
continue;
|
||||
}
|
||||
let (pos_constraint, neg_constraint) = self.predicate_constraints(node.atom);
|
||||
|
||||
let projected = if pos_constraint.is_none() && neg_constraint.is_none() {
|
||||
|
||||
Reference in new issue
Block a user