Compare commits
22 Commits
micha/ty-p
...
david/unre
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
500b9a2691 | ||
|
|
15476be531 | ||
|
|
e7a361699d | ||
|
|
a218e1901b | ||
|
|
f108043d2d | ||
|
|
77b45aeee9 | ||
|
|
c6f4c106b0 | ||
|
|
dc55b4c8a2 | ||
|
|
41d19c3c29 | ||
|
|
99d44299e8 | ||
|
|
32ad489d79 | ||
|
|
a3e7e7d8b6 | ||
|
|
aea4bbbb30 | ||
|
|
167e445243 | ||
|
|
dccfd6e4f8 | ||
|
|
eb4ae2b910 | ||
|
|
5be842b1c3 | ||
|
|
2a21d79ec4 | ||
|
|
1964ecdbb7 | ||
|
|
6d167672f1 | ||
|
|
ae45b897ea | ||
|
|
90f48f45b0 |
@@ -5,11 +5,11 @@
|
||||
pub enum TargetVersion {
|
||||
Py37,
|
||||
Py38,
|
||||
#[default]
|
||||
Py39,
|
||||
Py310,
|
||||
Py311,
|
||||
Py312,
|
||||
#[default]
|
||||
Py313,
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ if (x := 1) and bool_instance():
|
||||
if True or (x := 1):
|
||||
# TODO: infer that the second arm is never executed, and raise `unresolved-reference`.
|
||||
# error: [possibly-unresolved-reference]
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
reveal_type(x) # revealed: Never
|
||||
|
||||
if True and (x := 1):
|
||||
# TODO: infer that the second arm is always executed, do not raise a diagnostic
|
||||
|
||||
@@ -0,0 +1,303 @@
|
||||
# Statically-known branches
|
||||
|
||||
## Always false
|
||||
|
||||
### If
|
||||
|
||||
```py
|
||||
x = 1
|
||||
|
||||
if False:
|
||||
x = 2
|
||||
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
```
|
||||
|
||||
### Else
|
||||
|
||||
```py
|
||||
x = 1
|
||||
|
||||
if True:
|
||||
pass
|
||||
else:
|
||||
x = 2
|
||||
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
```
|
||||
|
||||
## Always true
|
||||
|
||||
### If
|
||||
|
||||
```py
|
||||
x = 1
|
||||
|
||||
if True:
|
||||
x = 2
|
||||
|
||||
reveal_type(x) # revealed: Literal[2]
|
||||
```
|
||||
|
||||
### Else
|
||||
|
||||
```py
|
||||
x = 1
|
||||
|
||||
if False:
|
||||
pass
|
||||
else:
|
||||
x = 2
|
||||
|
||||
reveal_type(x) # revealed: Literal[2]
|
||||
```
|
||||
|
||||
## Combination
|
||||
|
||||
```py
|
||||
x = 1
|
||||
|
||||
if True:
|
||||
x = 2
|
||||
else:
|
||||
x = 3
|
||||
|
||||
reveal_type(x) # revealed: Literal[2]
|
||||
```
|
||||
|
||||
## Nested
|
||||
|
||||
```py path=nested_if_true_if_true.py
|
||||
x = 1
|
||||
|
||||
if True:
|
||||
if True:
|
||||
x = 2
|
||||
else:
|
||||
x = 3
|
||||
else:
|
||||
x = 4
|
||||
|
||||
reveal_type(x) # revealed: Literal[2]
|
||||
```
|
||||
|
||||
```py path=nested_if_true_if_false.py
|
||||
x = 1
|
||||
|
||||
if True:
|
||||
if False:
|
||||
x = 2
|
||||
else:
|
||||
x = 3
|
||||
else:
|
||||
x = 4
|
||||
|
||||
reveal_type(x) # revealed: Literal[3]
|
||||
```
|
||||
|
||||
```py path=nested_if_true_if_bool.py
|
||||
def flag() -> bool: ...
|
||||
|
||||
x = 1
|
||||
|
||||
if True:
|
||||
if flag():
|
||||
x = 2
|
||||
else:
|
||||
x = 3
|
||||
else:
|
||||
x = 4
|
||||
|
||||
reveal_type(x) # revealed: Literal[2, 3]
|
||||
```
|
||||
|
||||
```py path=nested_if_bool_if_true.py
|
||||
def flag() -> bool: ...
|
||||
|
||||
x = 1
|
||||
|
||||
if flag():
|
||||
if True:
|
||||
x = 2
|
||||
else:
|
||||
x = 3
|
||||
else:
|
||||
x = 4
|
||||
|
||||
reveal_type(x) # revealed: Literal[2, 4]
|
||||
```
|
||||
|
||||
```py path=nested_else_if_true.py
|
||||
x = 1
|
||||
|
||||
if False:
|
||||
x = 2
|
||||
else:
|
||||
if True:
|
||||
x = 3
|
||||
else:
|
||||
x = 4
|
||||
|
||||
reveal_type(x) # revealed: Literal[3]
|
||||
```
|
||||
|
||||
```py path=nested_else_if_false.py
|
||||
x = 1
|
||||
|
||||
if False:
|
||||
x = 2
|
||||
else:
|
||||
if False:
|
||||
x = 3
|
||||
else:
|
||||
x = 4
|
||||
|
||||
reveal_type(x) # revealed: Literal[4]
|
||||
```
|
||||
|
||||
```py path=nested_else_if_bool.py
|
||||
def flag() -> bool: ...
|
||||
|
||||
x = 1
|
||||
|
||||
if False:
|
||||
x = 2
|
||||
else:
|
||||
if flag():
|
||||
x = 3
|
||||
else:
|
||||
x = 4
|
||||
|
||||
reveal_type(x) # revealed: Literal[3, 4]
|
||||
```
|
||||
|
||||
## If-expressions
|
||||
|
||||
### Always true
|
||||
|
||||
```py
|
||||
x = 1 if True else 2
|
||||
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
```
|
||||
|
||||
### Always false
|
||||
|
||||
```py
|
||||
x = 1 if False else 2
|
||||
|
||||
reveal_type(x) # revealed: Literal[2]
|
||||
```
|
||||
|
||||
## Boolean expressions
|
||||
|
||||
### Always true
|
||||
|
||||
```py
|
||||
(x := 1) == 1 or (x := 2)
|
||||
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
```
|
||||
|
||||
### Always false
|
||||
|
||||
```py
|
||||
(x := 1) == 0 or (x := 2)
|
||||
|
||||
reveal_type(x) # revealed: Literal[2]
|
||||
```
|
||||
|
||||
## Conditional declarations
|
||||
|
||||
```py path=if_false.py
|
||||
x: str
|
||||
|
||||
if False:
|
||||
x: int
|
||||
|
||||
def f() -> None:
|
||||
reveal_type(x) # revealed: str
|
||||
```
|
||||
|
||||
```py path=if_true_else.py
|
||||
x: str
|
||||
|
||||
if True:
|
||||
pass
|
||||
else:
|
||||
x: int
|
||||
|
||||
def f() -> None:
|
||||
reveal_type(x) # revealed: str
|
||||
```
|
||||
|
||||
```py path=if_true.py
|
||||
x: str
|
||||
|
||||
if True:
|
||||
x: int
|
||||
|
||||
def f() -> None:
|
||||
reveal_type(x) # revealed: int
|
||||
```
|
||||
|
||||
```py path=if_false_else.py
|
||||
x: str
|
||||
|
||||
if False:
|
||||
pass
|
||||
else:
|
||||
x: int
|
||||
|
||||
def f() -> None:
|
||||
reveal_type(x) # revealed: int
|
||||
```
|
||||
|
||||
```py path=if_bool.py
|
||||
def flag() -> bool: ...
|
||||
|
||||
x: str
|
||||
|
||||
if flag():
|
||||
x: int
|
||||
|
||||
def f() -> None:
|
||||
reveal_type(x) # revealed: str | int
|
||||
```
|
||||
|
||||
## Conditionally defined functions
|
||||
|
||||
```py
|
||||
def f() -> int: ...
|
||||
def g() -> int: ...
|
||||
|
||||
if True:
|
||||
def f() -> str: ...
|
||||
|
||||
else:
|
||||
def g() -> str: ...
|
||||
|
||||
reveal_type(f()) # revealed: str
|
||||
reveal_type(g()) # revealed: int
|
||||
```
|
||||
|
||||
## Conditionally defined class attributes
|
||||
|
||||
```py
|
||||
class C:
|
||||
if True:
|
||||
x: int = 1
|
||||
else:
|
||||
x: str = "a"
|
||||
|
||||
reveal_type(C.x) # revealed: int
|
||||
```
|
||||
|
||||
## TODO
|
||||
|
||||
- declarations vs bindings => NoDefault: NoDefaultType
|
||||
- conditional imports
|
||||
- conditional class definitions
|
||||
- compare with tests in if.md=>Statically known branches
|
||||
- boundness
|
||||
- TODO in `issubclass.md`
|
||||
@@ -49,14 +49,14 @@ sometimes not:
|
||||
```py
|
||||
import sys
|
||||
|
||||
reveal_type(sys.version_info >= (3, 9, 1)) # revealed: bool
|
||||
reveal_type(sys.version_info >= (3, 9, 1, "final", 0)) # revealed: bool
|
||||
reveal_type(sys.version_info >= (3, 9, 1)) # revealed: Literal[True]
|
||||
reveal_type(sys.version_info >= (3, 9, 1, "final", 0)) # revealed: Literal[True]
|
||||
|
||||
# TODO: While this won't fail at runtime, the user has probably made a mistake
|
||||
# if they're comparing a tuple of length >5 with `sys.version_info`
|
||||
# (`sys.version_info` is a tuple of length 5). It might be worth
|
||||
# emitting a lint diagnostic of some kind warning them about the probable error?
|
||||
reveal_type(sys.version_info >= (3, 9, 1, "final", 0, 5)) # revealed: bool
|
||||
reveal_type(sys.version_info >= (3, 9, 1, "final", 0, 5)) # revealed: Literal[True]
|
||||
|
||||
reveal_type(sys.version_info == (3, 8, 1, "finallllll", 0)) # revealed: Literal[False]
|
||||
```
|
||||
@@ -102,8 +102,8 @@ The fields of `sys.version_info` can be accessed by name:
|
||||
import sys
|
||||
|
||||
reveal_type(sys.version_info.major >= 3) # revealed: Literal[True]
|
||||
reveal_type(sys.version_info.minor >= 9) # revealed: Literal[True]
|
||||
reveal_type(sys.version_info.minor >= 10) # revealed: Literal[False]
|
||||
reveal_type(sys.version_info.minor >= 13) # revealed: Literal[True]
|
||||
reveal_type(sys.version_info.minor >= 14) # revealed: Literal[False]
|
||||
```
|
||||
|
||||
But the `micro`, `releaselevel` and `serial` fields are inferred as `@Todo` until we support
|
||||
@@ -125,14 +125,14 @@ The fields of `sys.version_info` can be accessed by index or by slice:
|
||||
import sys
|
||||
|
||||
reveal_type(sys.version_info[0] < 3) # revealed: Literal[False]
|
||||
reveal_type(sys.version_info[1] > 9) # revealed: Literal[False]
|
||||
reveal_type(sys.version_info[1] > 13) # revealed: Literal[False]
|
||||
|
||||
# revealed: tuple[Literal[3], Literal[9], int, Literal["alpha", "beta", "candidate", "final"], int]
|
||||
# revealed: tuple[Literal[3], Literal[13], int, Literal["alpha", "beta", "candidate", "final"], int]
|
||||
reveal_type(sys.version_info[:5])
|
||||
|
||||
reveal_type(sys.version_info[:2] >= (3, 9)) # revealed: Literal[True]
|
||||
reveal_type(sys.version_info[0:2] >= (3, 10)) # revealed: Literal[False]
|
||||
reveal_type(sys.version_info[:3] >= (3, 10, 1)) # revealed: Literal[False]
|
||||
reveal_type(sys.version_info[:2] >= (3, 13)) # revealed: Literal[True]
|
||||
reveal_type(sys.version_info[0:2] >= (3, 14)) # revealed: Literal[False]
|
||||
reveal_type(sys.version_info[:3] >= (3, 14, 1)) # revealed: Literal[False]
|
||||
reveal_type(sys.version_info[3] == "final") # revealed: bool
|
||||
reveal_type(sys.version_info[3] == "finalllllll") # revealed: Literal[False]
|
||||
```
|
||||
|
||||
@@ -39,7 +39,7 @@ impl PythonVersion {
|
||||
|
||||
impl Default for PythonVersion {
|
||||
fn default() -> Self {
|
||||
Self::PY39
|
||||
Self::PY313 // TODO: temporarily changed to 3.13 to activate all sys.version_info branches
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1229,4 +1229,32 @@ match 1:
|
||||
|
||||
assert!(matches!(binding.kind(&db), DefinitionKind::For(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn if_statement() {
|
||||
let TestCase { db, file } = test_case(
|
||||
"
|
||||
x = False
|
||||
|
||||
if True:
|
||||
x: bool
|
||||
",
|
||||
);
|
||||
|
||||
let index = semantic_index(&db, file);
|
||||
// let global_table = index.symbol_table(FileScopeId::global());
|
||||
|
||||
let use_def = index.use_def_map(FileScopeId::global());
|
||||
|
||||
// use_def
|
||||
|
||||
use_def.print(&db);
|
||||
|
||||
assert!(false);
|
||||
// let binding = use_def
|
||||
// .first_public_binding(global_table.symbol_id_by_name(name).expect("symbol exists"))
|
||||
// .expect("Expected with item definition for {name}");
|
||||
// assert!(matches!(binding.kind(&db), DefinitionKind::WithItem(_)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ use crate::semantic_index::symbol::{
|
||||
FileScopeId, NodeWithScopeKey, NodeWithScopeRef, Scope, ScopeId, ScopedSymbolId,
|
||||
SymbolTableBuilder,
|
||||
};
|
||||
use crate::semantic_index::use_def::{FlowSnapshot, UseDefMapBuilder};
|
||||
use crate::semantic_index::use_def::{ActiveConstraintsSnapshot, FlowSnapshot, UseDefMapBuilder};
|
||||
use crate::semantic_index::SemanticIndex;
|
||||
use crate::unpack::Unpack;
|
||||
use crate::Db;
|
||||
@@ -200,12 +200,20 @@ impl<'db> SemanticIndexBuilder<'db> {
|
||||
self.current_use_def_map().snapshot()
|
||||
}
|
||||
|
||||
fn flow_restore(&mut self, state: FlowSnapshot) {
|
||||
self.current_use_def_map_mut().restore(state);
|
||||
fn constraints_snapshot(&self) -> ActiveConstraintsSnapshot {
|
||||
self.current_use_def_map().constraints_snapshot()
|
||||
}
|
||||
|
||||
fn flow_merge(&mut self, state: FlowSnapshot) {
|
||||
fn flow_restore(&mut self, state: FlowSnapshot, active_constraints: ActiveConstraintsSnapshot) {
|
||||
self.current_use_def_map_mut().restore(state);
|
||||
self.current_use_def_map_mut()
|
||||
.restore_constraints(active_constraints);
|
||||
}
|
||||
|
||||
fn flow_merge(&mut self, state: FlowSnapshot, active_constraints: ActiveConstraintsSnapshot) {
|
||||
self.current_use_def_map_mut().merge(state);
|
||||
self.current_use_def_map_mut()
|
||||
.restore_constraints(active_constraints);
|
||||
}
|
||||
|
||||
fn add_symbol(&mut self, name: Name) -> ScopedSymbolId {
|
||||
@@ -765,6 +773,7 @@ where
|
||||
ast::Stmt::If(node) => {
|
||||
self.visit_expr(&node.test);
|
||||
let pre_if = self.flow_snapshot();
|
||||
let pre_if_constraints = self.constraints_snapshot();
|
||||
let constraint = self.record_expression_constraint(&node.test);
|
||||
let mut constraints = vec![constraint];
|
||||
self.visit_body(&node.body);
|
||||
@@ -790,7 +799,7 @@ where
|
||||
post_clauses.push(self.flow_snapshot());
|
||||
// we can only take an elif/else branch if none of the previous ones were
|
||||
// taken, so the block entry state is always `pre_if`
|
||||
self.flow_restore(pre_if.clone());
|
||||
self.flow_restore(pre_if.clone(), pre_if_constraints.clone());
|
||||
for constraint in &constraints {
|
||||
self.record_negated_constraint(*constraint);
|
||||
}
|
||||
@@ -801,7 +810,7 @@ where
|
||||
self.visit_body(clause_body);
|
||||
}
|
||||
for post_clause_state in post_clauses {
|
||||
self.flow_merge(post_clause_state);
|
||||
self.flow_merge(post_clause_state, pre_if_constraints.clone());
|
||||
}
|
||||
}
|
||||
ast::Stmt::While(ast::StmtWhile {
|
||||
@@ -813,6 +822,7 @@ where
|
||||
self.visit_expr(test);
|
||||
|
||||
let pre_loop = self.flow_snapshot();
|
||||
let pre_loop_constraints = self.constraints_snapshot();
|
||||
|
||||
// Save aside any break states from an outer loop
|
||||
let saved_break_states = std::mem::take(&mut self.loop_break_states);
|
||||
@@ -831,13 +841,13 @@ where
|
||||
|
||||
// We may execute the `else` clause without ever executing the body, so merge in
|
||||
// the pre-loop state before visiting `else`.
|
||||
self.flow_merge(pre_loop);
|
||||
self.flow_merge(pre_loop, pre_loop_constraints.clone());
|
||||
self.visit_body(orelse);
|
||||
|
||||
// Breaking out of a while loop bypasses the `else` clause, so merge in the break
|
||||
// states after visiting `else`.
|
||||
for break_state in break_states {
|
||||
self.flow_merge(break_state);
|
||||
self.flow_merge(break_state, pre_loop_constraints.clone()); // TODO?
|
||||
}
|
||||
}
|
||||
ast::Stmt::With(ast::StmtWith {
|
||||
@@ -880,6 +890,7 @@ where
|
||||
self.visit_expr(iter);
|
||||
|
||||
let pre_loop = self.flow_snapshot();
|
||||
let pre_loop_constraints = self.constraints_snapshot();
|
||||
let saved_break_states = std::mem::take(&mut self.loop_break_states);
|
||||
|
||||
debug_assert_eq!(&self.current_assignments, &[]);
|
||||
@@ -900,13 +911,13 @@ where
|
||||
|
||||
// We may execute the `else` clause without ever executing the body, so merge in
|
||||
// the pre-loop state before visiting `else`.
|
||||
self.flow_merge(pre_loop);
|
||||
self.flow_merge(pre_loop, pre_loop_constraints.clone());
|
||||
self.visit_body(orelse);
|
||||
|
||||
// Breaking out of a `for` loop bypasses the `else` clause, so merge in the break
|
||||
// states after visiting `else`.
|
||||
for break_state in break_states {
|
||||
self.flow_merge(break_state);
|
||||
self.flow_merge(break_state, pre_loop_constraints.clone());
|
||||
}
|
||||
}
|
||||
ast::Stmt::Match(ast::StmtMatch {
|
||||
@@ -918,6 +929,7 @@ where
|
||||
self.visit_expr(subject);
|
||||
|
||||
let after_subject = self.flow_snapshot();
|
||||
let after_subject_cs = self.constraints_snapshot();
|
||||
let Some((first, remaining)) = cases.split_first() else {
|
||||
return;
|
||||
};
|
||||
@@ -927,18 +939,18 @@ where
|
||||
let mut post_case_snapshots = vec![];
|
||||
for case in remaining {
|
||||
post_case_snapshots.push(self.flow_snapshot());
|
||||
self.flow_restore(after_subject.clone());
|
||||
self.flow_restore(after_subject.clone(), after_subject_cs.clone());
|
||||
self.add_pattern_constraint(subject, &case.pattern);
|
||||
self.visit_match_case(case);
|
||||
}
|
||||
for post_clause_state in post_case_snapshots {
|
||||
self.flow_merge(post_clause_state);
|
||||
self.flow_merge(post_clause_state, after_subject_cs.clone());
|
||||
}
|
||||
if !cases
|
||||
.last()
|
||||
.is_some_and(|case| case.guard.is_none() && case.pattern.is_wildcard())
|
||||
{
|
||||
self.flow_merge(after_subject);
|
||||
self.flow_merge(after_subject, after_subject_cs.clone());
|
||||
}
|
||||
}
|
||||
ast::Stmt::Try(ast::StmtTry {
|
||||
@@ -956,6 +968,7 @@ where
|
||||
// We will merge this state with all of the intermediate
|
||||
// states during the `try` block before visiting those suites.
|
||||
let pre_try_block_state = self.flow_snapshot();
|
||||
let pre_try_block_constraints = self.constraints_snapshot();
|
||||
|
||||
self.try_node_context_stack_manager.push_context();
|
||||
|
||||
@@ -976,14 +989,17 @@ where
|
||||
// as there necessarily must have been 0 `except` blocks executed
|
||||
// if we hit the `else` block.
|
||||
let post_try_block_state = self.flow_snapshot();
|
||||
let post_try_block_constraints = self.constraints_snapshot();
|
||||
|
||||
// Prepare for visiting the `except` block(s)
|
||||
self.flow_restore(pre_try_block_state);
|
||||
self.flow_restore(pre_try_block_state, pre_try_block_constraints.clone());
|
||||
for state in try_block_snapshots {
|
||||
self.flow_merge(state);
|
||||
self.flow_merge(state, pre_try_block_constraints.clone());
|
||||
// TODO?
|
||||
}
|
||||
|
||||
let pre_except_state = self.flow_snapshot();
|
||||
let pre_except_constraints = self.constraints_snapshot();
|
||||
let num_handlers = handlers.len();
|
||||
|
||||
for (i, except_handler) in handlers.iter().enumerate() {
|
||||
@@ -1022,19 +1038,22 @@ where
|
||||
// as we'll immediately call `self.flow_restore()` to a different state
|
||||
// as soon as this loop over the handlers terminates.
|
||||
if i < (num_handlers - 1) {
|
||||
self.flow_restore(pre_except_state.clone());
|
||||
self.flow_restore(
|
||||
pre_except_state.clone(),
|
||||
pre_except_constraints.clone(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// If we get to the `else` block, we know that 0 of the `except` blocks can have been executed,
|
||||
// and the entire `try` block must have been executed:
|
||||
self.flow_restore(post_try_block_state);
|
||||
self.flow_restore(post_try_block_state, post_try_block_constraints);
|
||||
}
|
||||
|
||||
self.visit_body(orelse);
|
||||
|
||||
for post_except_state in post_except_states {
|
||||
self.flow_merge(post_except_state);
|
||||
self.flow_merge(post_except_state, pre_try_block_constraints.clone());
|
||||
}
|
||||
|
||||
// TODO: there's lots of complexity here that isn't yet handled by our model.
|
||||
@@ -1191,19 +1210,17 @@ where
|
||||
ast::Expr::If(ast::ExprIf {
|
||||
body, test, orelse, ..
|
||||
}) => {
|
||||
// TODO detect statically known truthy or falsy test (via type inference, not naive
|
||||
// AST inspection, so we can't simplify here, need to record test expression for
|
||||
// later checking)
|
||||
self.visit_expr(test);
|
||||
let pre_if = self.flow_snapshot();
|
||||
let pre_if_constraints = self.constraints_snapshot();
|
||||
let constraint = self.record_expression_constraint(test);
|
||||
self.visit_expr(body);
|
||||
let post_body = self.flow_snapshot();
|
||||
self.flow_restore(pre_if);
|
||||
self.flow_restore(pre_if, pre_if_constraints.clone());
|
||||
|
||||
self.record_negated_constraint(constraint);
|
||||
self.visit_expr(orelse);
|
||||
self.flow_merge(post_body);
|
||||
self.flow_merge(post_body, pre_if_constraints);
|
||||
}
|
||||
ast::Expr::ListComp(
|
||||
list_comprehension @ ast::ExprListComp {
|
||||
@@ -1264,7 +1281,7 @@ where
|
||||
// AST inspection, so we can't simplify here, need to record test expression for
|
||||
// later checking)
|
||||
let mut snapshots = vec![];
|
||||
|
||||
let pre_op_constraints = self.constraints_snapshot();
|
||||
for (index, value) in values.iter().enumerate() {
|
||||
self.visit_expr(value);
|
||||
// In the last value we don't need to take a snapshot nor add a constraint
|
||||
@@ -1279,7 +1296,7 @@ where
|
||||
}
|
||||
}
|
||||
for snapshot in snapshots {
|
||||
self.flow_merge(snapshot);
|
||||
self.flow_merge(snapshot, pre_op_constraints.clone());
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
|
||||
@@ -221,6 +221,8 @@
|
||||
//! snapshot, and merging a snapshot into the current state. The logic using these methods lives in
|
||||
//! [`SemanticIndexBuilder`](crate::semantic_index::builder::SemanticIndexBuilder), e.g. where it
|
||||
//! visits a `StmtIf` node.
|
||||
use std::collections::HashSet;
|
||||
|
||||
use self::symbol_state::{
|
||||
BindingIdWithConstraintsIterator, ConstraintIdIterator, DeclarationIdIterator,
|
||||
ScopedConstraintId, ScopedDefinitionId, SymbolBindings, SymbolDeclarations, SymbolState,
|
||||
@@ -268,6 +270,109 @@ pub(crate) struct UseDefMap<'db> {
|
||||
}
|
||||
|
||||
impl<'db> UseDefMap<'db> {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn print(&self, db: &dyn crate::db::Db) {
|
||||
use crate::semantic_index::constraint::ConstraintNode;
|
||||
|
||||
println!("all_definitions:");
|
||||
println!("================");
|
||||
|
||||
for (id, d) in self.all_definitions.iter_enumerated() {
|
||||
println!(
|
||||
"{:?}: {:?} {:?} {:?}",
|
||||
id,
|
||||
d.category(db),
|
||||
d.scope(db),
|
||||
d.symbol(db),
|
||||
);
|
||||
println!(" {:?}", d.kind(db));
|
||||
println!();
|
||||
}
|
||||
|
||||
println!("all_constraints:");
|
||||
println!("================");
|
||||
|
||||
for (id, c) in self.all_constraints.iter_enumerated() {
|
||||
println!("{:?}: {:?}", id, c.node);
|
||||
if let ConstraintNode::Expression(e) = c.node {
|
||||
println!(" {:?}", e.node_ref(db));
|
||||
}
|
||||
}
|
||||
|
||||
println!();
|
||||
|
||||
println!("bindings_by_use:");
|
||||
println!("================");
|
||||
|
||||
for (id, bindings) in self.bindings_by_use.iter_enumerated() {
|
||||
println!("{:?}:", id);
|
||||
for binding in bindings.iter() {
|
||||
let definition = self.all_definitions[binding.definition];
|
||||
let mut constraint_ids = binding.constraint_ids.peekable();
|
||||
let mut active_constraint_ids =
|
||||
binding.constraints_active_at_binding_ids.peekable();
|
||||
|
||||
println!(" * {:?}", definition);
|
||||
|
||||
if constraint_ids.peek().is_some() {
|
||||
println!(" Constraints:");
|
||||
for constraint_id in constraint_ids {
|
||||
println!(" {:?}", self.all_constraints[constraint_id]);
|
||||
}
|
||||
} else {
|
||||
println!(" No constraints");
|
||||
}
|
||||
|
||||
println!();
|
||||
|
||||
if active_constraint_ids.peek().is_some() {
|
||||
println!(" Active constraints at binding:");
|
||||
for constraint_id in active_constraint_ids {
|
||||
println!(" {:?}", self.all_constraints[constraint_id]);
|
||||
}
|
||||
} else {
|
||||
println!(" No active constraints at binding");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!();
|
||||
|
||||
println!("public_symbols:");
|
||||
println!("================");
|
||||
|
||||
for (id, symbol) in self.public_symbols.iter_enumerated() {
|
||||
println!("{:?}:", id);
|
||||
println!(" * Bindings:");
|
||||
for binding in symbol.bindings().iter() {
|
||||
let definition = self.all_definitions[binding.definition];
|
||||
let mut constraint_ids = binding.constraint_ids.peekable();
|
||||
|
||||
println!(" {:?}", definition);
|
||||
|
||||
if constraint_ids.peek().is_some() {
|
||||
println!(" Constraints:");
|
||||
for constraint_id in constraint_ids {
|
||||
println!(" {:?}", self.all_constraints[constraint_id]);
|
||||
}
|
||||
} else {
|
||||
println!(" No constraints");
|
||||
}
|
||||
}
|
||||
|
||||
println!(" * Declarations:");
|
||||
for (declaration, _) in symbol.declarations().iter() {
|
||||
let definition = self.all_definitions[declaration];
|
||||
println!(" {:?}", definition);
|
||||
}
|
||||
|
||||
println!();
|
||||
}
|
||||
|
||||
println!();
|
||||
println!();
|
||||
}
|
||||
|
||||
pub(crate) fn bindings_at_use(
|
||||
&self,
|
||||
use_id: ScopedUseId,
|
||||
@@ -352,6 +457,7 @@ impl<'db> UseDefMap<'db> {
|
||||
) -> DeclarationsIterator<'a, 'db> {
|
||||
DeclarationsIterator {
|
||||
all_definitions: &self.all_definitions,
|
||||
all_constraints: &self.all_constraints,
|
||||
inner: declarations.iter(),
|
||||
may_be_undeclared: declarations.may_be_undeclared(),
|
||||
}
|
||||
@@ -365,7 +471,7 @@ enum SymbolDefinitions {
|
||||
Declarations(SymbolDeclarations),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct BindingWithConstraintsIterator<'map, 'db> {
|
||||
all_definitions: &'map IndexVec<ScopedDefinitionId, Definition<'db>>,
|
||||
all_constraints: &'map IndexVec<ScopedConstraintId, Constraint<'db>>,
|
||||
@@ -384,6 +490,10 @@ impl<'map, 'db> Iterator for BindingWithConstraintsIterator<'map, 'db> {
|
||||
all_constraints: self.all_constraints,
|
||||
constraint_ids: def_id_with_constraints.constraint_ids,
|
||||
},
|
||||
constraints_active_at_binding: ConstraintsIterator {
|
||||
all_constraints: self.all_constraints,
|
||||
constraint_ids: def_id_with_constraints.constraints_active_at_binding_ids,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -393,8 +503,10 @@ impl std::iter::FusedIterator for BindingWithConstraintsIterator<'_, '_> {}
|
||||
pub(crate) struct BindingWithConstraints<'map, 'db> {
|
||||
pub(crate) binding: Definition<'db>,
|
||||
pub(crate) constraints: ConstraintsIterator<'map, 'db>,
|
||||
pub(crate) constraints_active_at_binding: ConstraintsIterator<'map, 'db>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct ConstraintsIterator<'map, 'db> {
|
||||
all_constraints: &'map IndexVec<ScopedConstraintId, Constraint<'db>>,
|
||||
constraint_ids: ConstraintIdIterator<'map>,
|
||||
@@ -414,6 +526,7 @@ impl std::iter::FusedIterator for ConstraintsIterator<'_, '_> {}
|
||||
|
||||
pub(crate) struct DeclarationsIterator<'map, 'db> {
|
||||
all_definitions: &'map IndexVec<ScopedDefinitionId, Definition<'db>>,
|
||||
all_constraints: &'map IndexVec<ScopedConstraintId, Constraint<'db>>,
|
||||
inner: DeclarationIdIterator<'map>,
|
||||
may_be_undeclared: bool,
|
||||
}
|
||||
@@ -425,10 +538,18 @@ impl DeclarationsIterator<'_, '_> {
|
||||
}
|
||||
|
||||
impl<'map, 'db> Iterator for DeclarationsIterator<'map, 'db> {
|
||||
type Item = Definition<'db>;
|
||||
type Item = (Definition<'db>, ConstraintsIterator<'map, 'db>);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.inner.next().map(|def_id| self.all_definitions[def_id])
|
||||
self.inner.next().map(|(def_id, constraints)| {
|
||||
(
|
||||
self.all_definitions[def_id],
|
||||
ConstraintsIterator {
|
||||
all_constraints: self.all_constraints,
|
||||
constraint_ids: constraints,
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -440,6 +561,9 @@ pub(super) struct FlowSnapshot {
|
||||
symbol_states: IndexVec<ScopedSymbolId, SymbolState>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(super) struct ActiveConstraintsSnapshot(HashSet<ScopedConstraintId>);
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(super) struct UseDefMapBuilder<'db> {
|
||||
/// Append-only array of [`Definition`].
|
||||
@@ -448,6 +572,8 @@ pub(super) struct UseDefMapBuilder<'db> {
|
||||
/// Append-only array of [`Constraint`].
|
||||
all_constraints: IndexVec<ScopedConstraintId, Constraint<'db>>,
|
||||
|
||||
active_constraints: HashSet<ScopedConstraintId>,
|
||||
|
||||
/// Live bindings at each so-far-recorded use.
|
||||
bindings_by_use: IndexVec<ScopedUseId, SymbolBindings>,
|
||||
|
||||
@@ -471,7 +597,7 @@ impl<'db> UseDefMapBuilder<'db> {
|
||||
binding,
|
||||
SymbolDefinitions::Declarations(symbol_state.declarations().clone()),
|
||||
);
|
||||
symbol_state.record_binding(def_id);
|
||||
symbol_state.record_binding(def_id, &self.active_constraints);
|
||||
}
|
||||
|
||||
pub(super) fn record_constraint(&mut self, constraint: Constraint<'db>) {
|
||||
@@ -479,6 +605,7 @@ impl<'db> UseDefMapBuilder<'db> {
|
||||
for state in &mut self.symbol_states {
|
||||
state.record_constraint(constraint_id);
|
||||
}
|
||||
self.active_constraints.insert(constraint_id);
|
||||
}
|
||||
|
||||
pub(super) fn record_declaration(
|
||||
@@ -492,7 +619,7 @@ impl<'db> UseDefMapBuilder<'db> {
|
||||
declaration,
|
||||
SymbolDefinitions::Bindings(symbol_state.bindings().clone()),
|
||||
);
|
||||
symbol_state.record_declaration(def_id);
|
||||
symbol_state.record_declaration(def_id, &self.active_constraints);
|
||||
}
|
||||
|
||||
pub(super) fn record_declaration_and_binding(
|
||||
@@ -503,8 +630,8 @@ impl<'db> UseDefMapBuilder<'db> {
|
||||
// We don't need to store anything in self.definitions_by_definition.
|
||||
let def_id = self.all_definitions.push(definition);
|
||||
let symbol_state = &mut self.symbol_states[symbol];
|
||||
symbol_state.record_declaration(def_id);
|
||||
symbol_state.record_binding(def_id);
|
||||
symbol_state.record_declaration(def_id, &self.active_constraints);
|
||||
symbol_state.record_binding(def_id, &self.active_constraints);
|
||||
}
|
||||
|
||||
pub(super) fn record_use(&mut self, symbol: ScopedSymbolId, use_id: ScopedUseId) {
|
||||
@@ -523,6 +650,10 @@ impl<'db> UseDefMapBuilder<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn constraints_snapshot(&self) -> ActiveConstraintsSnapshot {
|
||||
ActiveConstraintsSnapshot(self.active_constraints.clone())
|
||||
}
|
||||
|
||||
/// Restore the current builder symbols state to the given snapshot.
|
||||
pub(super) fn restore(&mut self, snapshot: FlowSnapshot) {
|
||||
// We never remove symbols from `symbol_states` (it's an IndexVec, and the symbol
|
||||
@@ -541,6 +672,10 @@ impl<'db> UseDefMapBuilder<'db> {
|
||||
.resize(num_symbols, SymbolState::undefined());
|
||||
}
|
||||
|
||||
pub(super) fn restore_constraints(&mut self, snapshot: ActiveConstraintsSnapshot) {
|
||||
self.active_constraints = snapshot.0;
|
||||
}
|
||||
|
||||
/// Merge the given snapshot into the current state, reflecting that we might have taken either
|
||||
/// path to get here. The new state for each symbol should include definitions from both the
|
||||
/// prior state and the snapshot.
|
||||
|
||||
@@ -122,7 +122,7 @@ impl<const B: usize> BitSet<B> {
|
||||
}
|
||||
|
||||
/// Iterator over values in a [`BitSet`].
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub(super) struct BitSetIterator<'a, const B: usize> {
|
||||
/// The blocks we are iterating over.
|
||||
blocks: &'a [u64],
|
||||
|
||||
@@ -43,6 +43,8 @@
|
||||
//!
|
||||
//! Tracking live declarations is simpler, since constraints are not involved, but otherwise very
|
||||
//! similar to tracking live bindings.
|
||||
use std::collections::HashSet;
|
||||
|
||||
use super::bitset::{BitSet, BitSetIterator};
|
||||
use ruff_index::newtype_index;
|
||||
use smallvec::SmallVec;
|
||||
@@ -87,6 +89,8 @@ pub(super) struct SymbolDeclarations {
|
||||
/// [`BitSet`]: which declarations (as [`ScopedDefinitionId`]) can reach the current location?
|
||||
live_declarations: Declarations,
|
||||
|
||||
constraints_active_at_declaration: Constraints, // TODO: rename to constraints_active_at_declaration
|
||||
|
||||
/// Could the symbol be un-declared at this point?
|
||||
may_be_undeclared: bool,
|
||||
}
|
||||
@@ -95,14 +99,27 @@ impl SymbolDeclarations {
|
||||
fn undeclared() -> Self {
|
||||
Self {
|
||||
live_declarations: Declarations::default(),
|
||||
constraints_active_at_declaration: Constraints::default(),
|
||||
may_be_undeclared: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Record a newly-encountered declaration for this symbol.
|
||||
fn record_declaration(&mut self, declaration_id: ScopedDefinitionId) {
|
||||
fn record_declaration(
|
||||
&mut self,
|
||||
declaration_id: ScopedDefinitionId,
|
||||
active_constraints: &HashSet<ScopedConstraintId>,
|
||||
) {
|
||||
self.live_declarations = Declarations::with(declaration_id.into());
|
||||
self.may_be_undeclared = false;
|
||||
|
||||
// TODO: unify code with below
|
||||
self.constraints_active_at_declaration = Constraints::with_capacity(1);
|
||||
self.constraints_active_at_declaration
|
||||
.push(BitSet::default());
|
||||
for active_constraint_id in active_constraints {
|
||||
self.constraints_active_at_declaration[0].insert(active_constraint_id.as_u32());
|
||||
}
|
||||
}
|
||||
|
||||
/// Add undeclared as a possibility for this symbol.
|
||||
@@ -114,6 +131,7 @@ impl SymbolDeclarations {
|
||||
pub(super) fn iter(&self) -> DeclarationIdIterator {
|
||||
DeclarationIdIterator {
|
||||
inner: self.live_declarations.iter(),
|
||||
constraints_active_at_binding: self.constraints_active_at_declaration.iter(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,6 +156,8 @@ pub(super) struct SymbolBindings {
|
||||
/// binding in `live_bindings`.
|
||||
constraints: Constraints,
|
||||
|
||||
constraints_active_at_binding: Constraints,
|
||||
|
||||
/// Could the symbol be unbound at this point?
|
||||
may_be_unbound: bool,
|
||||
}
|
||||
@@ -147,6 +167,7 @@ impl SymbolBindings {
|
||||
Self {
|
||||
live_bindings: Bindings::default(),
|
||||
constraints: Constraints::default(),
|
||||
constraints_active_at_binding: Constraints::default(),
|
||||
may_be_unbound: true,
|
||||
}
|
||||
}
|
||||
@@ -157,12 +178,21 @@ impl SymbolBindings {
|
||||
}
|
||||
|
||||
/// Record a newly-encountered binding for this symbol.
|
||||
pub(super) fn record_binding(&mut self, binding_id: ScopedDefinitionId) {
|
||||
pub(super) fn record_binding(
|
||||
&mut self,
|
||||
binding_id: ScopedDefinitionId,
|
||||
active_constraints: &HashSet<ScopedConstraintId>,
|
||||
) {
|
||||
// The new binding replaces all previous live bindings in this path, and has no
|
||||
// constraints.
|
||||
self.live_bindings = Bindings::with(binding_id.into());
|
||||
self.constraints = Constraints::with_capacity(1);
|
||||
self.constraints.push(BitSet::default());
|
||||
self.constraints_active_at_binding = Constraints::with_capacity(1);
|
||||
self.constraints_active_at_binding.push(BitSet::default());
|
||||
for active_constraint_id in active_constraints {
|
||||
self.constraints_active_at_binding[0].insert(active_constraint_id.as_u32());
|
||||
}
|
||||
self.may_be_unbound = false;
|
||||
}
|
||||
|
||||
@@ -178,6 +208,7 @@ impl SymbolBindings {
|
||||
BindingIdWithConstraintsIterator {
|
||||
definitions: self.live_bindings.iter(),
|
||||
constraints: self.constraints.iter(),
|
||||
constraints_active_at_binding: self.constraints_active_at_binding.iter(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,8 +238,12 @@ impl SymbolState {
|
||||
}
|
||||
|
||||
/// Record a newly-encountered binding for this symbol.
|
||||
pub(super) fn record_binding(&mut self, binding_id: ScopedDefinitionId) {
|
||||
self.bindings.record_binding(binding_id);
|
||||
pub(super) fn record_binding(
|
||||
&mut self,
|
||||
binding_id: ScopedDefinitionId,
|
||||
active_constraints: &HashSet<ScopedConstraintId>,
|
||||
) {
|
||||
self.bindings.record_binding(binding_id, active_constraints);
|
||||
}
|
||||
|
||||
/// Add given constraint to all live bindings.
|
||||
@@ -222,8 +257,13 @@ impl SymbolState {
|
||||
}
|
||||
|
||||
/// Record a newly-encountered declaration of this symbol.
|
||||
pub(super) fn record_declaration(&mut self, declaration_id: ScopedDefinitionId) {
|
||||
self.declarations.record_declaration(declaration_id);
|
||||
pub(super) fn record_declaration(
|
||||
&mut self,
|
||||
declaration_id: ScopedDefinitionId,
|
||||
active_constraints: &HashSet<ScopedConstraintId>,
|
||||
) {
|
||||
self.declarations
|
||||
.record_declaration(declaration_id, active_constraints);
|
||||
}
|
||||
|
||||
/// Merge another [`SymbolState`] into this one.
|
||||
@@ -232,24 +272,93 @@ impl SymbolState {
|
||||
bindings: SymbolBindings {
|
||||
live_bindings: Bindings::default(),
|
||||
constraints: Constraints::default(),
|
||||
constraints_active_at_binding: Constraints::default(), // TODO
|
||||
may_be_unbound: self.bindings.may_be_unbound || b.bindings.may_be_unbound,
|
||||
},
|
||||
declarations: SymbolDeclarations {
|
||||
live_declarations: self.declarations.live_declarations.clone(),
|
||||
constraints_active_at_declaration: Constraints::default(), // TODO
|
||||
may_be_undeclared: self.declarations.may_be_undeclared
|
||||
|| b.declarations.may_be_undeclared,
|
||||
},
|
||||
};
|
||||
|
||||
// let mut constraints_active_at_binding = BitSet::default();
|
||||
// for active_constraint_id in active_constraints.0 {
|
||||
// constraints_active_at_binding.insert(active_constraint_id.as_u32());
|
||||
// }
|
||||
|
||||
std::mem::swap(&mut a, self);
|
||||
self.declarations
|
||||
.live_declarations
|
||||
.union(&b.declarations.live_declarations);
|
||||
// self.declarations
|
||||
// .live_declarations
|
||||
// .union(&b.declarations.live_declarations);
|
||||
|
||||
let mut a_decls_iter = a.declarations.live_declarations.iter();
|
||||
let mut b_decls_iter = b.declarations.live_declarations.iter();
|
||||
let mut a_constraints_active_at_declaration_iter =
|
||||
a.declarations.constraints_active_at_declaration.into_iter();
|
||||
let mut b_constraints_active_at_declaration_iter =
|
||||
b.declarations.constraints_active_at_declaration.into_iter();
|
||||
|
||||
let mut opt_a_decl: Option<u32> = a_decls_iter.next();
|
||||
let mut opt_b_decl: Option<u32> = b_decls_iter.next();
|
||||
|
||||
let push = |decl,
|
||||
constraints_active_at_declaration_iter: &mut ConstraintsIntoIterator,
|
||||
merged: &mut Self| {
|
||||
merged.declarations.live_declarations.insert(decl);
|
||||
let constraints_active_at_binding = constraints_active_at_declaration_iter
|
||||
.next()
|
||||
.expect("declarations and constraints_active_at_binding length mismatch");
|
||||
merged
|
||||
.declarations
|
||||
.constraints_active_at_declaration
|
||||
.push(constraints_active_at_binding);
|
||||
};
|
||||
|
||||
loop {
|
||||
match (opt_a_decl, opt_b_decl) {
|
||||
(Some(a_decl), Some(b_decl)) => match a_decl.cmp(&b_decl) {
|
||||
std::cmp::Ordering::Less => {
|
||||
push(a_decl, &mut a_constraints_active_at_declaration_iter, self);
|
||||
opt_a_decl = a_decls_iter.next();
|
||||
}
|
||||
std::cmp::Ordering::Greater => {
|
||||
push(b_decl, &mut b_constraints_active_at_declaration_iter, self);
|
||||
opt_b_decl = b_decls_iter.next();
|
||||
}
|
||||
std::cmp::Ordering::Equal => {
|
||||
push(a_decl, &mut b_constraints_active_at_declaration_iter, self);
|
||||
self.declarations
|
||||
.constraints_active_at_declaration
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
.intersect(&a_constraints_active_at_declaration_iter.next().unwrap());
|
||||
|
||||
opt_a_decl = a_decls_iter.next();
|
||||
opt_b_decl = b_decls_iter.next();
|
||||
}
|
||||
},
|
||||
(Some(a_decl), None) => {
|
||||
push(a_decl, &mut a_constraints_active_at_declaration_iter, self);
|
||||
opt_a_decl = a_decls_iter.next();
|
||||
}
|
||||
(None, Some(b_decl)) => {
|
||||
push(b_decl, &mut b_constraints_active_at_declaration_iter, self);
|
||||
opt_b_decl = b_decls_iter.next();
|
||||
}
|
||||
(None, None) => break,
|
||||
}
|
||||
}
|
||||
|
||||
let mut a_defs_iter = a.bindings.live_bindings.iter();
|
||||
let mut b_defs_iter = b.bindings.live_bindings.iter();
|
||||
let mut a_constraints_iter = a.bindings.constraints.into_iter();
|
||||
let mut b_constraints_iter = b.bindings.constraints.into_iter();
|
||||
let mut a_constraints_active_at_binding_iter =
|
||||
a.bindings.constraints_active_at_binding.into_iter();
|
||||
let mut b_constraints_active_at_binding_iter =
|
||||
b.bindings.constraints_active_at_binding.into_iter();
|
||||
|
||||
let mut opt_a_def: Option<u32> = a_defs_iter.next();
|
||||
let mut opt_b_def: Option<u32> = b_defs_iter.next();
|
||||
@@ -261,7 +370,10 @@ impl SymbolState {
|
||||
// path is irrelevant.
|
||||
|
||||
// Helper to push `def`, with constraints in `constraints_iter`, onto `self`.
|
||||
let push = |def, constraints_iter: &mut ConstraintsIntoIterator, merged: &mut Self| {
|
||||
let push = |def,
|
||||
constraints_iter: &mut ConstraintsIntoIterator,
|
||||
constraints_active_at_binding_iter: &mut ConstraintsIntoIterator,
|
||||
merged: &mut Self| {
|
||||
merged.bindings.live_bindings.insert(def);
|
||||
// SAFETY: we only ever create SymbolState with either no definitions and no constraint
|
||||
// bitsets (`::unbound`) or one definition and one constraint bitset (`::with`), and
|
||||
@@ -271,7 +383,14 @@ impl SymbolState {
|
||||
let constraints = constraints_iter
|
||||
.next()
|
||||
.expect("definitions and constraints length mismatch");
|
||||
let constraints_active_at_binding = constraints_active_at_binding_iter
|
||||
.next()
|
||||
.expect("definitions and constraints_active_at_binding length mismatch");
|
||||
merged.bindings.constraints.push(constraints);
|
||||
merged
|
||||
.bindings
|
||||
.constraints_active_at_binding
|
||||
.push(constraints_active_at_binding);
|
||||
};
|
||||
|
||||
loop {
|
||||
@@ -279,17 +398,32 @@ impl SymbolState {
|
||||
(Some(a_def), Some(b_def)) => match a_def.cmp(&b_def) {
|
||||
std::cmp::Ordering::Less => {
|
||||
// Next definition ID is only in `a`, push it to `self` and advance `a`.
|
||||
push(a_def, &mut a_constraints_iter, self);
|
||||
push(
|
||||
a_def,
|
||||
&mut a_constraints_iter,
|
||||
&mut a_constraints_active_at_binding_iter,
|
||||
self,
|
||||
);
|
||||
opt_a_def = a_defs_iter.next();
|
||||
}
|
||||
std::cmp::Ordering::Greater => {
|
||||
// Next definition ID is only in `b`, push it to `self` and advance `b`.
|
||||
push(b_def, &mut b_constraints_iter, self);
|
||||
push(
|
||||
b_def,
|
||||
&mut b_constraints_iter,
|
||||
&mut b_constraints_active_at_binding_iter,
|
||||
self,
|
||||
);
|
||||
opt_b_def = b_defs_iter.next();
|
||||
}
|
||||
std::cmp::Ordering::Equal => {
|
||||
// Next definition is in both; push to `self` and intersect constraints.
|
||||
push(a_def, &mut b_constraints_iter, self);
|
||||
push(
|
||||
a_def,
|
||||
&mut b_constraints_iter,
|
||||
&mut b_constraints_active_at_binding_iter,
|
||||
self,
|
||||
);
|
||||
// SAFETY: we only ever create SymbolState with either no definitions and
|
||||
// no constraint bitsets (`::unbound`) or one definition and one constraint
|
||||
// bitset (`::with`), and `::merge` always pushes one definition and one
|
||||
@@ -298,6 +432,11 @@ impl SymbolState {
|
||||
let a_constraints = a_constraints_iter
|
||||
.next()
|
||||
.expect("definitions and constraints length mismatch");
|
||||
// let _a_constraints_active_at_binding =
|
||||
// a_constraints_active_at_binding_iter.next().expect(
|
||||
// "definitions and constraints_active_at_binding length mismatch",
|
||||
// ); // TODO: perform check that we see the same constraints in both paths
|
||||
|
||||
// If the same definition is visible through both paths, any constraint
|
||||
// that applies on only one path is irrelevant to the resulting type from
|
||||
// unioning the two paths, so we intersect the constraints.
|
||||
@@ -306,18 +445,29 @@ impl SymbolState {
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
.intersect(&a_constraints);
|
||||
|
||||
opt_a_def = a_defs_iter.next();
|
||||
opt_b_def = b_defs_iter.next();
|
||||
}
|
||||
},
|
||||
(Some(a_def), None) => {
|
||||
// We've exhausted `b`, just push the def from `a` and move on to the next.
|
||||
push(a_def, &mut a_constraints_iter, self);
|
||||
push(
|
||||
a_def,
|
||||
&mut a_constraints_iter,
|
||||
&mut a_constraints_active_at_binding_iter,
|
||||
self,
|
||||
);
|
||||
opt_a_def = a_defs_iter.next();
|
||||
}
|
||||
(None, Some(b_def)) => {
|
||||
// We've exhausted `a`, just push the def from `b` and move on to the next.
|
||||
push(b_def, &mut b_constraints_iter, self);
|
||||
push(
|
||||
b_def,
|
||||
&mut b_constraints_iter,
|
||||
&mut b_constraints_active_at_binding_iter,
|
||||
self,
|
||||
);
|
||||
opt_b_def = b_defs_iter.next();
|
||||
}
|
||||
(None, None) => break,
|
||||
@@ -353,26 +503,37 @@ impl Default for SymbolState {
|
||||
pub(super) struct BindingIdWithConstraints<'a> {
|
||||
pub(super) definition: ScopedDefinitionId,
|
||||
pub(super) constraint_ids: ConstraintIdIterator<'a>,
|
||||
pub(super) constraints_active_at_binding_ids: ConstraintIdIterator<'a>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub(super) struct BindingIdWithConstraintsIterator<'a> {
|
||||
definitions: BindingsIterator<'a>,
|
||||
constraints: ConstraintsIterator<'a>,
|
||||
constraints_active_at_binding: ConstraintsIterator<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for BindingIdWithConstraintsIterator<'a> {
|
||||
type Item = BindingIdWithConstraints<'a>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match (self.definitions.next(), self.constraints.next()) {
|
||||
(None, None) => None,
|
||||
(Some(def), Some(constraints)) => Some(BindingIdWithConstraints {
|
||||
definition: ScopedDefinitionId::from_u32(def),
|
||||
constraint_ids: ConstraintIdIterator {
|
||||
wrapped: constraints.iter(),
|
||||
},
|
||||
}),
|
||||
match (
|
||||
self.definitions.next(),
|
||||
self.constraints.next(),
|
||||
self.constraints_active_at_binding.next(),
|
||||
) {
|
||||
(None, None, None) => None,
|
||||
(Some(def), Some(constraints), Some(constraints_active_at_binding)) => {
|
||||
Some(BindingIdWithConstraints {
|
||||
definition: ScopedDefinitionId::from_u32(def),
|
||||
constraint_ids: ConstraintIdIterator {
|
||||
wrapped: constraints.iter(),
|
||||
},
|
||||
constraints_active_at_binding_ids: ConstraintIdIterator {
|
||||
wrapped: constraints_active_at_binding.iter(),
|
||||
},
|
||||
})
|
||||
}
|
||||
// SAFETY: see above.
|
||||
_ => unreachable!("definitions and constraints length mismatch"),
|
||||
}
|
||||
@@ -381,7 +542,7 @@ impl<'a> Iterator for BindingIdWithConstraintsIterator<'a> {
|
||||
|
||||
impl std::iter::FusedIterator for BindingIdWithConstraintsIterator<'_> {}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub(super) struct ConstraintIdIterator<'a> {
|
||||
wrapped: BitSetIterator<'a, INLINE_CONSTRAINT_BLOCKS>,
|
||||
}
|
||||
@@ -399,13 +560,25 @@ impl std::iter::FusedIterator for ConstraintIdIterator<'_> {}
|
||||
#[derive(Debug)]
|
||||
pub(super) struct DeclarationIdIterator<'a> {
|
||||
inner: DeclarationsIterator<'a>,
|
||||
constraints_active_at_binding: ConstraintsIterator<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for DeclarationIdIterator<'a> {
|
||||
type Item = ScopedDefinitionId;
|
||||
type Item = (ScopedDefinitionId, ConstraintIdIterator<'a>);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.inner.next().map(ScopedDefinitionId::from_u32)
|
||||
// self.inner.next().map(ScopedDefinitionId::from_u32)
|
||||
match (self.inner.next(), self.constraints_active_at_binding.next()) {
|
||||
(None, None) => None,
|
||||
(Some(declaration), Some(constraints_active_at_binding)) => Some((
|
||||
ScopedDefinitionId::from_u32(declaration),
|
||||
ConstraintIdIterator {
|
||||
wrapped: constraints_active_at_binding.iter(),
|
||||
},
|
||||
)),
|
||||
// SAFETY: see above.
|
||||
_ => unreachable!("declarations and constraints_active_at_binding length mismatch"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -413,7 +586,7 @@ impl std::iter::FusedIterator for DeclarationIdIterator<'_> {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{ScopedConstraintId, ScopedDefinitionId, SymbolState};
|
||||
use super::{ScopedConstraintId, SymbolState};
|
||||
|
||||
fn assert_bindings(symbol: &SymbolState, may_be_unbound: bool, expected: &[&str]) {
|
||||
assert_eq!(symbol.may_be_unbound(), may_be_unbound);
|
||||
@@ -445,7 +618,7 @@ mod tests {
|
||||
let actual = symbol
|
||||
.declarations()
|
||||
.iter()
|
||||
.map(ScopedDefinitionId::as_u32)
|
||||
.map(|(d, _)| d.as_u32()) // TODO: constraints
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
@@ -457,76 +630,76 @@ mod tests {
|
||||
assert_bindings(&sym, true, &[]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with() {
|
||||
let mut sym = SymbolState::undefined();
|
||||
sym.record_binding(ScopedDefinitionId::from_u32(0));
|
||||
// #[test]
|
||||
// fn with() {
|
||||
// let mut sym = SymbolState::undefined();
|
||||
// sym.record_binding(ScopedDefinitionId::from_u32(0));
|
||||
|
||||
assert_bindings(&sym, false, &["0<>"]);
|
||||
}
|
||||
// assert_bindings(&sym, false, &["0<>"]);
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn set_may_be_unbound() {
|
||||
let mut sym = SymbolState::undefined();
|
||||
sym.record_binding(ScopedDefinitionId::from_u32(0));
|
||||
sym.set_may_be_unbound();
|
||||
// #[test]
|
||||
// fn set_may_be_unbound() {
|
||||
// let mut sym = SymbolState::undefined();
|
||||
// sym.record_binding(ScopedDefinitionId::from_u32(0));
|
||||
// sym.set_may_be_unbound();
|
||||
|
||||
assert_bindings(&sym, true, &["0<>"]);
|
||||
}
|
||||
// assert_bindings(&sym, true, &["0<>"]);
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn record_constraint() {
|
||||
let mut sym = SymbolState::undefined();
|
||||
sym.record_binding(ScopedDefinitionId::from_u32(0));
|
||||
sym.record_constraint(ScopedConstraintId::from_u32(0));
|
||||
// #[test]
|
||||
// fn record_constraint() {
|
||||
// let mut sym = SymbolState::undefined();
|
||||
// sym.record_binding(ScopedDefinitionId::from_u32(0));
|
||||
// sym.record_constraint(ScopedConstraintId::from_u32(0));
|
||||
|
||||
assert_bindings(&sym, false, &["0<0>"]);
|
||||
}
|
||||
// assert_bindings(&sym, false, &["0<0>"]);
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn merge() {
|
||||
// merging the same definition with the same constraint keeps the constraint
|
||||
let mut sym0a = SymbolState::undefined();
|
||||
sym0a.record_binding(ScopedDefinitionId::from_u32(0));
|
||||
sym0a.record_constraint(ScopedConstraintId::from_u32(0));
|
||||
// #[test]
|
||||
// fn merge() {
|
||||
// // merging the same definition with the same constraint keeps the constraint
|
||||
// let mut sym0a = SymbolState::undefined();
|
||||
// sym0a.record_binding(ScopedDefinitionId::from_u32(0));
|
||||
// sym0a.record_constraint(ScopedConstraintId::from_u32(0));
|
||||
|
||||
let mut sym0b = SymbolState::undefined();
|
||||
sym0b.record_binding(ScopedDefinitionId::from_u32(0));
|
||||
sym0b.record_constraint(ScopedConstraintId::from_u32(0));
|
||||
// let mut sym0b = SymbolState::undefined();
|
||||
// sym0b.record_binding(ScopedDefinitionId::from_u32(0));
|
||||
// sym0b.record_constraint(ScopedConstraintId::from_u32(0));
|
||||
|
||||
sym0a.merge(sym0b);
|
||||
let mut sym0 = sym0a;
|
||||
assert_bindings(&sym0, false, &["0<0>"]);
|
||||
// sym0a.merge(sym0b);
|
||||
// let mut sym0 = sym0a;
|
||||
// assert_bindings(&sym0, false, &["0<0>"]);
|
||||
|
||||
// merging the same definition with differing constraints drops all constraints
|
||||
let mut sym1a = SymbolState::undefined();
|
||||
sym1a.record_binding(ScopedDefinitionId::from_u32(1));
|
||||
sym1a.record_constraint(ScopedConstraintId::from_u32(1));
|
||||
// // merging the same definition with differing constraints drops all constraints
|
||||
// let mut sym1a = SymbolState::undefined();
|
||||
// sym1a.record_binding(ScopedDefinitionId::from_u32(1));
|
||||
// sym1a.record_constraint(ScopedConstraintId::from_u32(1));
|
||||
|
||||
let mut sym1b = SymbolState::undefined();
|
||||
sym1b.record_binding(ScopedDefinitionId::from_u32(1));
|
||||
sym1b.record_constraint(ScopedConstraintId::from_u32(2));
|
||||
// let mut sym1b = SymbolState::undefined();
|
||||
// sym1b.record_binding(ScopedDefinitionId::from_u32(1));
|
||||
// sym1b.record_constraint(ScopedConstraintId::from_u32(2));
|
||||
|
||||
sym1a.merge(sym1b);
|
||||
let sym1 = sym1a;
|
||||
assert_bindings(&sym1, false, &["1<>"]);
|
||||
// sym1a.merge(sym1b);
|
||||
// let sym1 = sym1a;
|
||||
// assert_bindings(&sym1, false, &["1<>"]);
|
||||
|
||||
// merging a constrained definition with unbound keeps both
|
||||
let mut sym2a = SymbolState::undefined();
|
||||
sym2a.record_binding(ScopedDefinitionId::from_u32(2));
|
||||
sym2a.record_constraint(ScopedConstraintId::from_u32(3));
|
||||
// // merging a constrained definition with unbound keeps both
|
||||
// let mut sym2a = SymbolState::undefined();
|
||||
// sym2a.record_binding(ScopedDefinitionId::from_u32(2));
|
||||
// sym2a.record_constraint(ScopedConstraintId::from_u32(3));
|
||||
|
||||
let sym2b = SymbolState::undefined();
|
||||
// let sym2b = SymbolState::undefined();
|
||||
|
||||
sym2a.merge(sym2b);
|
||||
let sym2 = sym2a;
|
||||
assert_bindings(&sym2, true, &["2<3>"]);
|
||||
// sym2a.merge(sym2b);
|
||||
// let sym2 = sym2a;
|
||||
// assert_bindings(&sym2, true, &["2<3>"]);
|
||||
|
||||
// merging different definitions keeps them each with their existing constraints
|
||||
sym0.merge(sym2);
|
||||
let sym = sym0;
|
||||
assert_bindings(&sym, true, &["0<0>", "2<3>"]);
|
||||
}
|
||||
// // merging different definitions keeps them each with their existing constraints
|
||||
// sym0.merge(sym2);
|
||||
// let sym = sym0;
|
||||
// assert_bindings(&sym, true, &["0<0>", "2<3>"]);
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn no_declaration() {
|
||||
@@ -535,54 +708,54 @@ mod tests {
|
||||
assert_declarations(&sym, true, &[]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_declaration() {
|
||||
let mut sym = SymbolState::undefined();
|
||||
sym.record_declaration(ScopedDefinitionId::from_u32(1));
|
||||
// #[test]
|
||||
// fn record_declaration() {
|
||||
// let mut sym = SymbolState::undefined();
|
||||
// sym.record_declaration(ScopedDefinitionId::from_u32(1));
|
||||
|
||||
assert_declarations(&sym, false, &[1]);
|
||||
}
|
||||
// assert_declarations(&sym, false, &[1]);
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn record_declaration_override() {
|
||||
let mut sym = SymbolState::undefined();
|
||||
sym.record_declaration(ScopedDefinitionId::from_u32(1));
|
||||
sym.record_declaration(ScopedDefinitionId::from_u32(2));
|
||||
// #[test]
|
||||
// fn record_declaration_override() {
|
||||
// let mut sym = SymbolState::undefined();
|
||||
// sym.record_declaration(ScopedDefinitionId::from_u32(1));
|
||||
// sym.record_declaration(ScopedDefinitionId::from_u32(2));
|
||||
|
||||
assert_declarations(&sym, false, &[2]);
|
||||
}
|
||||
// assert_declarations(&sym, false, &[2]);
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn record_declaration_merge() {
|
||||
let mut sym = SymbolState::undefined();
|
||||
sym.record_declaration(ScopedDefinitionId::from_u32(1));
|
||||
// #[test]
|
||||
// fn record_declaration_merge() {
|
||||
// let mut sym = SymbolState::undefined();
|
||||
// sym.record_declaration(ScopedDefinitionId::from_u32(1));
|
||||
|
||||
let mut sym2 = SymbolState::undefined();
|
||||
sym2.record_declaration(ScopedDefinitionId::from_u32(2));
|
||||
// let mut sym2 = SymbolState::undefined();
|
||||
// sym2.record_declaration(ScopedDefinitionId::from_u32(2));
|
||||
|
||||
sym.merge(sym2);
|
||||
// sym.merge(sym2);
|
||||
|
||||
assert_declarations(&sym, false, &[1, 2]);
|
||||
}
|
||||
// assert_declarations(&sym, false, &[1, 2]);
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn record_declaration_merge_partial_undeclared() {
|
||||
let mut sym = SymbolState::undefined();
|
||||
sym.record_declaration(ScopedDefinitionId::from_u32(1));
|
||||
// #[test]
|
||||
// fn record_declaration_merge_partial_undeclared() {
|
||||
// let mut sym = SymbolState::undefined();
|
||||
// sym.record_declaration(ScopedDefinitionId::from_u32(1));
|
||||
|
||||
let sym2 = SymbolState::undefined();
|
||||
// let sym2 = SymbolState::undefined();
|
||||
|
||||
sym.merge(sym2);
|
||||
// sym.merge(sym2);
|
||||
|
||||
assert_declarations(&sym, true, &[1]);
|
||||
}
|
||||
// assert_declarations(&sym, true, &[1]);
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn set_may_be_undeclared() {
|
||||
let mut sym = SymbolState::undefined();
|
||||
sym.record_declaration(ScopedDefinitionId::from_u32(0));
|
||||
sym.set_may_be_undeclared();
|
||||
// #[test]
|
||||
// fn set_may_be_undeclared() {
|
||||
// let mut sym = SymbolState::undefined();
|
||||
// sym.record_declaration(ScopedDefinitionId::from_u32(0));
|
||||
// sym.set_may_be_undeclared();
|
||||
|
||||
assert_declarations(&sym, true, &[0]);
|
||||
}
|
||||
// assert_declarations(&sym, true, &[0]);
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ pub(crate) use self::infer::{
|
||||
pub(crate) use self::signatures::Signature;
|
||||
use crate::module_resolver::file_to_module;
|
||||
use crate::semantic_index::ast_ids::HasScopedExpressionId;
|
||||
use crate::semantic_index::constraint::ConstraintNode;
|
||||
use crate::semantic_index::definition::Definition;
|
||||
use crate::semantic_index::symbol::{self as symbol, ScopeId, ScopedSymbolId};
|
||||
use crate::semantic_index::{
|
||||
@@ -222,6 +223,12 @@ fn definition_expression_ty<'db>(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum UnconditionallyVisible {
|
||||
Yes,
|
||||
No,
|
||||
}
|
||||
|
||||
/// Infer the combined type of an iterator of bindings.
|
||||
///
|
||||
/// Will return a union if there is more than one binding.
|
||||
@@ -229,29 +236,88 @@ fn bindings_ty<'db>(
|
||||
db: &'db dyn Db,
|
||||
bindings_with_constraints: BindingWithConstraintsIterator<'_, 'db>,
|
||||
) -> Option<Type<'db>> {
|
||||
let mut def_types = bindings_with_constraints.map(
|
||||
let def_types = bindings_with_constraints.map(
|
||||
|BindingWithConstraints {
|
||||
binding,
|
||||
constraints,
|
||||
constraints_active_at_binding,
|
||||
}| {
|
||||
let mut constraint_tys = constraints
|
||||
.filter_map(|constraint| narrowing_constraint(db, constraint, binding))
|
||||
.peekable();
|
||||
let test_expr_tys = || {
|
||||
constraints_active_at_binding.clone().map(|c| {
|
||||
let ty = if let ConstraintNode::Expression(test_expr) = c.node {
|
||||
let inference = infer_expression_types(db, test_expr);
|
||||
let scope = test_expr.scope(db);
|
||||
inference
|
||||
.expression_ty(test_expr.node_ref(db).scoped_expression_id(db, scope))
|
||||
} else {
|
||||
// TODO: handle other constraint nodes
|
||||
todo_type!()
|
||||
};
|
||||
|
||||
let binding_ty = binding_ty(db, binding);
|
||||
if constraint_tys.peek().is_some() {
|
||||
constraint_tys
|
||||
.fold(
|
||||
IntersectionBuilder::new(db).add_positive(binding_ty),
|
||||
IntersectionBuilder::add_positive,
|
||||
)
|
||||
.build()
|
||||
(c, ty)
|
||||
})
|
||||
};
|
||||
|
||||
if test_expr_tys().any(|(c, test_expr_ty)| {
|
||||
if c.is_positive {
|
||||
test_expr_ty.bool(db).is_always_false()
|
||||
} else {
|
||||
test_expr_ty.bool(db).is_always_true()
|
||||
}
|
||||
}) {
|
||||
// TODO: do we need to call binding_ty(…) even if we don't need the result?
|
||||
(Type::Never, UnconditionallyVisible::No)
|
||||
} else {
|
||||
binding_ty
|
||||
let mut test_expr_tys_iter = test_expr_tys().peekable();
|
||||
|
||||
let unconditionally_visible = if test_expr_tys_iter.peek().is_some()
|
||||
&& test_expr_tys_iter.all(|(c, test_expr_ty)| {
|
||||
if c.is_positive {
|
||||
test_expr_ty.bool(db).is_always_true()
|
||||
} else {
|
||||
test_expr_ty.bool(db).is_always_false()
|
||||
}
|
||||
}) {
|
||||
UnconditionallyVisible::Yes
|
||||
} else {
|
||||
UnconditionallyVisible::No
|
||||
};
|
||||
|
||||
let mut constraint_tys = constraints
|
||||
.filter_map(|constraint| narrowing_constraint(db, constraint, binding))
|
||||
.peekable();
|
||||
|
||||
let binding_ty = binding_ty(db, binding);
|
||||
if constraint_tys.peek().is_some() {
|
||||
let intersection_ty = constraint_tys
|
||||
.fold(
|
||||
IntersectionBuilder::new(db).add_positive(binding_ty),
|
||||
IntersectionBuilder::add_positive,
|
||||
)
|
||||
.build();
|
||||
(intersection_ty, unconditionally_visible)
|
||||
} else {
|
||||
(binding_ty, unconditionally_visible)
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// TODO: get rid of all the collects and clean up, obviously
|
||||
let def_types: Vec<_> = def_types.collect();
|
||||
|
||||
// shrink the vector to only include everything from the last unconditionally visible binding
|
||||
let def_types: Vec<_> = def_types
|
||||
.iter()
|
||||
.rev()
|
||||
.take_while_inclusive(|(_, unconditionally_visible)| {
|
||||
*unconditionally_visible != UnconditionallyVisible::Yes
|
||||
})
|
||||
.map(|(ty, _)| *ty)
|
||||
.collect();
|
||||
|
||||
let mut def_types = def_types.into_iter().rev();
|
||||
|
||||
if let Some(first) = def_types.next() {
|
||||
if let Some(second) = def_types.next() {
|
||||
Some(UnionType::from_elements(
|
||||
@@ -287,7 +353,63 @@ fn declarations_ty<'db>(
|
||||
declarations: DeclarationsIterator<'_, 'db>,
|
||||
undeclared_ty: Option<Type<'db>>,
|
||||
) -> DeclaredTypeResult<'db> {
|
||||
let decl_types = declarations.map(|declaration| declaration_ty(db, declaration));
|
||||
let decl_types = declarations.map(|(declaration, constraints_active_at_declaration)| {
|
||||
let test_expr_tys = || {
|
||||
constraints_active_at_declaration.clone().map(|c| {
|
||||
let ty = if let ConstraintNode::Expression(test_expr) = c.node {
|
||||
let inference = infer_expression_types(db, test_expr);
|
||||
let scope = test_expr.scope(db);
|
||||
inference.expression_ty(test_expr.node_ref(db).scoped_expression_id(db, scope))
|
||||
} else {
|
||||
// TODO: handle other constraint nodes
|
||||
todo_type!()
|
||||
};
|
||||
|
||||
(c, ty)
|
||||
})
|
||||
};
|
||||
|
||||
if test_expr_tys().any(|(c, test_expr_ty)| {
|
||||
if c.is_positive {
|
||||
test_expr_ty.bool(db).is_always_false()
|
||||
} else {
|
||||
test_expr_ty.bool(db).is_always_true()
|
||||
}
|
||||
}) {
|
||||
(Type::Never, UnconditionallyVisible::No)
|
||||
} else {
|
||||
let mut test_expr_tys_iter = test_expr_tys().peekable();
|
||||
|
||||
if test_expr_tys_iter.peek().is_some()
|
||||
&& test_expr_tys_iter.all(|(c, test_expr_ty)| {
|
||||
if c.is_positive {
|
||||
test_expr_ty.bool(db).is_always_true()
|
||||
} else {
|
||||
test_expr_ty.bool(db).is_always_false()
|
||||
}
|
||||
})
|
||||
{
|
||||
(declaration_ty(db, declaration), UnconditionallyVisible::Yes)
|
||||
} else {
|
||||
(declaration_ty(db, declaration), UnconditionallyVisible::No)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: get rid of all the collects and clean up, obviously
|
||||
let decl_types: Vec<_> = decl_types.collect();
|
||||
|
||||
// shrink the vector to only include everything from the last unconditionally visible binding
|
||||
let decl_types: Vec<_> = decl_types
|
||||
.iter()
|
||||
.rev()
|
||||
.take_while_inclusive(|(_, unconditionally_visible)| {
|
||||
*unconditionally_visible != UnconditionallyVisible::Yes
|
||||
})
|
||||
.map(|(ty, _)| *ty)
|
||||
.collect();
|
||||
|
||||
let decl_types = decl_types.into_iter().rev();
|
||||
|
||||
let mut all_types = undeclared_ty.into_iter().chain(decl_types);
|
||||
|
||||
@@ -768,23 +890,7 @@ impl<'db> Type<'db> {
|
||||
|
||||
// TODO: Once we have support for final classes, we can establish that
|
||||
// `Type::SubclassOf('FinalClass')` is equivalent to `Type::ClassLiteral('FinalClass')`.
|
||||
|
||||
// TODO: The following is a workaround that is required to unify the two different versions
|
||||
// of `NoneType` and `NoDefaultType` in typeshed. This should not be required anymore once
|
||||
// we understand `sys.version_info` branches.
|
||||
self == other
|
||||
|| matches!((self, other), (Type::Todo(_), Type::Todo(_)))
|
||||
|| matches!((self, other),
|
||||
(
|
||||
Type::Instance(InstanceType { class: self_class }),
|
||||
Type::Instance(InstanceType { class: target_class })
|
||||
)
|
||||
if {
|
||||
let self_known = self_class.known(db);
|
||||
matches!(self_known, Some(KnownClass::NoneType | KnownClass::NoDefaultType))
|
||||
&& self_known == target_class.known(db)
|
||||
}
|
||||
)
|
||||
self == other || matches!((self, other), (Type::Todo(_), Type::Todo(_)))
|
||||
}
|
||||
|
||||
/// Return true if this type and `other` have no common elements.
|
||||
@@ -1763,13 +1869,13 @@ impl<'db> KnownClass {
|
||||
}
|
||||
|
||||
pub fn to_class_literal(self, db: &'db dyn Db) -> Type<'db> {
|
||||
core_module_symbol(db, self.canonical_module(), self.as_str())
|
||||
core_module_symbol(db, self.canonical_module(db), self.as_str())
|
||||
.ignore_possibly_unbound()
|
||||
.unwrap_or(Type::Unknown)
|
||||
}
|
||||
|
||||
/// Return the module in which we should look up the definition for this class
|
||||
pub(crate) const fn canonical_module(self) -> CoreStdlibModule {
|
||||
pub(crate) fn canonical_module(self, db: &'db dyn Db) -> CoreStdlibModule {
|
||||
match self {
|
||||
Self::Bool
|
||||
| Self::Object
|
||||
@@ -1787,10 +1893,18 @@ impl<'db> KnownClass {
|
||||
Self::GenericAlias | Self::ModuleType | Self::FunctionType => CoreStdlibModule::Types,
|
||||
Self::NoneType => CoreStdlibModule::Typeshed,
|
||||
Self::SpecialForm | Self::TypeVar | Self::TypeAliasType => CoreStdlibModule::Typing,
|
||||
// TODO when we understand sys.version_info, we will need an explicit fallback here,
|
||||
// because typing_extensions has a 3.13+ re-export for the `typing.NoDefault`
|
||||
// singleton, but not for `typing._NoDefaultType`
|
||||
Self::NoDefaultType => CoreStdlibModule::TypingExtensions,
|
||||
Self::NoDefaultType => {
|
||||
let python_version = Program::get(db).target_version(db);
|
||||
|
||||
// typing_extensions has a 3.13+ re-export for the `typing.NoDefault`
|
||||
// singleton, but not for `typing._NoDefaultType`. So we need to switch
|
||||
// to `typing.NoDefault` for newer versions:
|
||||
if python_version.major >= 3 && python_version.minor >= 13 {
|
||||
CoreStdlibModule::Typing
|
||||
} else {
|
||||
CoreStdlibModule::TypingExtensions
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1850,11 +1964,11 @@ impl<'db> KnownClass {
|
||||
};
|
||||
|
||||
let module = file_to_module(db, file)?;
|
||||
candidate.check_module(&module).then_some(candidate)
|
||||
candidate.check_module(db, &module).then_some(candidate)
|
||||
}
|
||||
|
||||
/// Return `true` if the module of `self` matches `module_name`
|
||||
fn check_module(self, module: &Module) -> bool {
|
||||
fn check_module(self, db: &dyn Db, module: &Module) -> bool {
|
||||
if !module.search_path().is_standard_library() {
|
||||
return false;
|
||||
}
|
||||
@@ -1874,7 +1988,7 @@ impl<'db> KnownClass {
|
||||
| Self::GenericAlias
|
||||
| Self::ModuleType
|
||||
| Self::VersionInfo
|
||||
| Self::FunctionType => module.name() == self.canonical_module().as_str(),
|
||||
| Self::FunctionType => module.name() == self.canonical_module(db).as_str(),
|
||||
Self::NoneType => matches!(module.name().as_str(), "_typeshed" | "types"),
|
||||
Self::SpecialForm | Self::TypeVar | Self::TypeAliasType | Self::NoDefaultType => {
|
||||
matches!(module.name().as_str(), "typing" | "typing_extensions")
|
||||
@@ -2396,6 +2510,14 @@ impl Truthiness {
|
||||
matches!(self, Truthiness::Ambiguous)
|
||||
}
|
||||
|
||||
const fn is_always_false(self) -> bool {
|
||||
matches!(self, Truthiness::AlwaysFalse)
|
||||
}
|
||||
|
||||
const fn is_always_true(self) -> bool {
|
||||
matches!(self, Truthiness::AlwaysTrue)
|
||||
}
|
||||
|
||||
const fn negate(self) -> Self {
|
||||
match self {
|
||||
Self::AlwaysTrue => Self::AlwaysFalse,
|
||||
@@ -3036,7 +3158,7 @@ pub(crate) mod tests {
|
||||
use ruff_python_ast as ast;
|
||||
use test_case::test_case;
|
||||
|
||||
pub(crate) fn setup_db() -> TestDb {
|
||||
pub(crate) fn setup_db_with_python_version(python_version: PythonVersion) -> TestDb {
|
||||
let db = TestDb::new();
|
||||
|
||||
let src_root = SystemPathBuf::from("/src");
|
||||
@@ -3047,7 +3169,7 @@ pub(crate) mod tests {
|
||||
Program::from_settings(
|
||||
&db,
|
||||
&ProgramSettings {
|
||||
target_version: PythonVersion::default(),
|
||||
target_version: python_version,
|
||||
search_paths: SearchPathSettings::new(src_root),
|
||||
},
|
||||
)
|
||||
@@ -3056,6 +3178,10 @@ pub(crate) mod tests {
|
||||
db
|
||||
}
|
||||
|
||||
pub(crate) fn setup_db() -> TestDb {
|
||||
setup_db_with_python_version(PythonVersion::default())
|
||||
}
|
||||
|
||||
/// A test representation of a type that can be transformed unambiguously into a real Type,
|
||||
/// given a db.
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -3503,13 +3629,23 @@ pub(crate) mod tests {
|
||||
#[test_case(Ty::None)]
|
||||
#[test_case(Ty::BooleanLiteral(true))]
|
||||
#[test_case(Ty::BooleanLiteral(false))]
|
||||
#[test_case(Ty::KnownClassInstance(KnownClass::NoDefaultType))]
|
||||
fn is_singleton(from: Ty) {
|
||||
let db = setup_db();
|
||||
|
||||
assert!(from.into_type(&db).is_singleton(&db));
|
||||
}
|
||||
|
||||
/// TODO: test documentation
|
||||
#[test_case(PythonVersion::PY312)]
|
||||
#[test_case(PythonVersion::PY313)]
|
||||
fn no_default_type_is_singleton(python_version: PythonVersion) {
|
||||
let db = setup_db_with_python_version(python_version);
|
||||
|
||||
let no_default = Ty::KnownClassInstance(KnownClass::NoDefaultType).into_type(&db);
|
||||
|
||||
assert!(no_default.is_singleton(&db));
|
||||
}
|
||||
|
||||
#[test_case(Ty::None)]
|
||||
#[test_case(Ty::BooleanLiteral(true))]
|
||||
#[test_case(Ty::IntLiteral(1))]
|
||||
|
||||
@@ -5031,7 +5031,7 @@ mod tests {
|
||||
|
||||
use crate::db::tests::TestDb;
|
||||
use crate::program::{Program, SearchPathSettings};
|
||||
use crate::python_version::PythonVersion;
|
||||
use crate::python_version::{self, PythonVersion};
|
||||
use crate::semantic_index::definition::Definition;
|
||||
use crate::semantic_index::symbol::FileScopeId;
|
||||
use crate::semantic_index::{global_scope, semantic_index, symbol_table, use_def_map};
|
||||
@@ -5041,10 +5041,11 @@ mod tests {
|
||||
use ruff_db::parsed::parsed_module;
|
||||
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
|
||||
use ruff_db::testing::assert_function_query_was_not_run;
|
||||
use test_case::test_case;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn setup_db() -> TestDb {
|
||||
fn setup_db_with_python_version(python_version: PythonVersion) -> TestDb {
|
||||
let db = TestDb::new();
|
||||
|
||||
let src_root = SystemPathBuf::from("/src");
|
||||
@@ -5055,7 +5056,7 @@ mod tests {
|
||||
Program::from_settings(
|
||||
&db,
|
||||
&ProgramSettings {
|
||||
target_version: PythonVersion::default(),
|
||||
target_version: python_version,
|
||||
search_paths: SearchPathSettings::new(src_root),
|
||||
},
|
||||
)
|
||||
@@ -5064,6 +5065,10 @@ mod tests {
|
||||
db
|
||||
}
|
||||
|
||||
fn setup_db() -> TestDb {
|
||||
setup_db_with_python_version(PythonVersion::default())
|
||||
}
|
||||
|
||||
fn setup_db_with_custom_typeshed<'a>(
|
||||
typeshed: &str,
|
||||
files: impl IntoIterator<Item = (&'a str, &'a str)>,
|
||||
@@ -5335,9 +5340,10 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ellipsis_type() -> anyhow::Result<()> {
|
||||
let mut db = setup_db();
|
||||
#[test_case(PythonVersion::PY39, "ellipsis")]
|
||||
#[test_case(PythonVersion::PY310, "EllipsisType")]
|
||||
fn ellipsis_type(version: PythonVersion, expected_type: &str) -> anyhow::Result<()> {
|
||||
let mut db = setup_db_with_python_version(version);
|
||||
|
||||
db.write_dedented(
|
||||
"src/a.py",
|
||||
@@ -5346,8 +5352,7 @@ mod tests {
|
||||
",
|
||||
)?;
|
||||
|
||||
// TODO: sys.version_info
|
||||
assert_public_ty(&db, "src/a.py", "x", "EllipsisType | ellipsis");
|
||||
assert_public_ty(&db, "src/a.py", "x", expected_type);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -185,11 +185,11 @@ impl Settings {
|
||||
pub enum TargetVersion {
|
||||
Py37,
|
||||
Py38,
|
||||
#[default]
|
||||
Py39,
|
||||
Py310,
|
||||
Py311,
|
||||
Py312,
|
||||
#[default]
|
||||
Py313,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
source: crates/red_knot_workspace/src/workspace/metadata.rs
|
||||
expression: "&workspace"
|
||||
snapshot_kind: text
|
||||
---
|
||||
WorkspaceMetadata(
|
||||
root: "/app",
|
||||
@@ -24,7 +23,7 @@ WorkspaceMetadata(
|
||||
program: ProgramSettings(
|
||||
target_version: PythonVersion(
|
||||
major: 3,
|
||||
minor: 9,
|
||||
minor: 13,
|
||||
),
|
||||
search_paths: SearchPathSettings(
|
||||
extra_paths: [],
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
source: crates/red_knot_workspace/src/workspace/metadata.rs
|
||||
expression: workspace
|
||||
snapshot_kind: text
|
||||
---
|
||||
WorkspaceMetadata(
|
||||
root: "/app",
|
||||
@@ -24,7 +23,7 @@ WorkspaceMetadata(
|
||||
program: ProgramSettings(
|
||||
target_version: PythonVersion(
|
||||
major: 3,
|
||||
minor: 9,
|
||||
minor: 13,
|
||||
),
|
||||
search_paths: SearchPathSettings(
|
||||
extra_paths: [],
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
source: crates/red_knot_workspace/src/workspace/metadata.rs
|
||||
expression: workspace
|
||||
snapshot_kind: text
|
||||
---
|
||||
WorkspaceMetadata(
|
||||
root: "/app",
|
||||
@@ -24,7 +23,7 @@ WorkspaceMetadata(
|
||||
program: ProgramSettings(
|
||||
target_version: PythonVersion(
|
||||
major: 3,
|
||||
minor: 9,
|
||||
minor: 13,
|
||||
),
|
||||
search_paths: SearchPathSettings(
|
||||
extra_paths: [],
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
source: crates/red_knot_workspace/src/workspace/metadata.rs
|
||||
expression: workspace
|
||||
snapshot_kind: text
|
||||
---
|
||||
WorkspaceMetadata(
|
||||
root: "/app",
|
||||
@@ -24,7 +23,7 @@ WorkspaceMetadata(
|
||||
program: ProgramSettings(
|
||||
target_version: PythonVersion(
|
||||
major: 3,
|
||||
minor: 9,
|
||||
minor: 13,
|
||||
),
|
||||
search_paths: SearchPathSettings(
|
||||
extra_paths: [],
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
source: crates/red_knot_workspace/src/workspace/metadata.rs
|
||||
expression: workspace
|
||||
snapshot_kind: text
|
||||
---
|
||||
WorkspaceMetadata(
|
||||
root: "/app",
|
||||
@@ -37,7 +36,7 @@ WorkspaceMetadata(
|
||||
program: ProgramSettings(
|
||||
target_version: PythonVersion(
|
||||
major: 3,
|
||||
minor: 9,
|
||||
minor: 13,
|
||||
),
|
||||
search_paths: SearchPathSettings(
|
||||
extra_paths: [],
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
source: crates/red_knot_workspace/src/workspace/metadata.rs
|
||||
expression: workspace
|
||||
snapshot_kind: text
|
||||
---
|
||||
WorkspaceMetadata(
|
||||
root: "/app",
|
||||
@@ -50,7 +49,7 @@ WorkspaceMetadata(
|
||||
program: ProgramSettings(
|
||||
target_version: PythonVersion(
|
||||
major: 3,
|
||||
minor: 9,
|
||||
minor: 13,
|
||||
),
|
||||
search_paths: SearchPathSettings(
|
||||
extra_paths: [],
|
||||
|
||||
Reference in New Issue
Block a user