[red-knot] Statically known branches
This commit is contained in:
@@ -47,7 +47,9 @@ def f():
|
||||
|
||||
## `typing.Never`
|
||||
|
||||
`typing.Never` is only available in Python 3.11 and later:
|
||||
`typing.Never` is only available in Python 3.11 and later.
|
||||
|
||||
### Python 3.11
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
@@ -57,8 +59,17 @@ python-version = "3.11"
|
||||
```py
|
||||
from typing import Never
|
||||
|
||||
x: Never
|
||||
|
||||
def f():
|
||||
reveal_type(x) # revealed: Never
|
||||
reveal_type(Never) # revealed: typing.Never
|
||||
```
|
||||
|
||||
### Python 3.10
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.10"
|
||||
```
|
||||
|
||||
```py
|
||||
# error: [unresolved-import]
|
||||
from typing import Never
|
||||
```
|
||||
|
||||
@@ -33,8 +33,6 @@ b: tuple[int] = (42,)
|
||||
c: tuple[str, int] = ("42", 42)
|
||||
d: tuple[tuple[str, str], tuple[int, int]] = (("foo", "foo"), (42, 42))
|
||||
e: tuple[str, ...] = ()
|
||||
# TODO: we should not emit this error
|
||||
# error: [call-possibly-unbound-method] "Method `__class_getitem__` of type `Literal[tuple]` is possibly unbound"
|
||||
f: tuple[str, *tuple[int, ...], bytes] = ("42", b"42")
|
||||
g: tuple[str, Unpack[tuple[int, ...]], bytes] = ("42", b"42")
|
||||
h: tuple[list[int], list[int]] = ([], [])
|
||||
|
||||
@@ -32,13 +32,10 @@ def _(flag: bool):
|
||||
|
||||
```py
|
||||
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]
|
||||
# error: [unresolved-reference]
|
||||
reveal_type(x) # revealed: Unknown
|
||||
|
||||
if True and (x := 1):
|
||||
# TODO: infer that the second arm is always executed, do not raise a diagnostic
|
||||
# error: [possibly-unresolved-reference]
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
```
|
||||
|
||||
|
||||
@@ -67,6 +67,6 @@ def _(flag: bool):
|
||||
def __call__(self) -> int: ...
|
||||
|
||||
a = NonCallable()
|
||||
# error: "Object of type `Literal[__call__] | Literal[1]` is not callable (due to union element `Literal[1]`)"
|
||||
reveal_type(a()) # revealed: int | Unknown
|
||||
# error: "Object of type `Literal[1] | Literal[__call__]` is not callable (due to union element `Literal[1]`)"
|
||||
reveal_type(a()) # revealed: Unknown | int
|
||||
```
|
||||
|
||||
@@ -7,7 +7,7 @@ def _(flag: bool):
|
||||
reveal_type(1 if flag else 2) # revealed: Literal[1, 2]
|
||||
```
|
||||
|
||||
## Statically known branches
|
||||
## Statically known conditions in if-expressions
|
||||
|
||||
```py
|
||||
reveal_type(1 if True else 2) # revealed: Literal[1]
|
||||
|
||||
@@ -1,7 +1,23 @@
|
||||
# Ellipsis literals
|
||||
|
||||
## Simple
|
||||
## Python 3.9
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.9"
|
||||
```
|
||||
|
||||
```py
|
||||
reveal_type(...) # revealed: EllipsisType | ellipsis
|
||||
reveal_type(...) # revealed: ellipsis
|
||||
```
|
||||
|
||||
## Python 3.10
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.10"
|
||||
```
|
||||
|
||||
```py
|
||||
reveal_type(...) # revealed: EllipsisType
|
||||
```
|
||||
|
||||
@@ -95,10 +95,14 @@ def _(t: type[object]):
|
||||
|
||||
### Handling of `None`
|
||||
|
||||
`types.NoneType` is only available in Python 3.10 and later:
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.10"
|
||||
```
|
||||
|
||||
```py
|
||||
# TODO: this error should ideally go away once we (1) understand `sys.version_info` branches,
|
||||
# and (2) set the target Python version for this test to 3.10.
|
||||
# error: [possibly-unbound-import] "Member `NoneType` of module `types` is possibly unbound"
|
||||
from types import NoneType
|
||||
|
||||
def _(flag: bool):
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -81,10 +81,7 @@ python-version = "3.9"
|
||||
```
|
||||
|
||||
```py
|
||||
# TODO:
|
||||
# * `tuple.__class_getitem__` is always bound on 3.9 (`sys.version_info`)
|
||||
# * `tuple[int, str]` is a valid base (generics)
|
||||
# error: [call-possibly-unbound-method] "Method `__class_getitem__` of type `Literal[tuple]` is possibly unbound"
|
||||
# TODO: `tuple[int, str]` is a valid base (generics)
|
||||
# error: [invalid-base] "Invalid class base with type `GenericAlias` (all bases must be a class, `Any`, `Unknown` or `Todo`)"
|
||||
class A(tuple[int, str]): ...
|
||||
|
||||
|
||||
@@ -27,9 +27,11 @@ pub mod definition;
|
||||
pub mod expression;
|
||||
pub mod symbol;
|
||||
mod use_def;
|
||||
pub(crate) mod visibility_constraint;
|
||||
|
||||
pub(crate) use self::use_def::{
|
||||
BindingWithConstraints, BindingWithConstraintsIterator, DeclarationsIterator,
|
||||
ScopedConstraintId, ScopedVisibilityConstraintId,
|
||||
};
|
||||
|
||||
type SymbolMap = hashbrown::HashMap<ScopedSymbolId, (), FxBuildHasher>;
|
||||
@@ -378,14 +380,16 @@ mod tests {
|
||||
impl UseDefMap<'_> {
|
||||
fn first_public_binding(&self, symbol: ScopedSymbolId) -> Option<Definition<'_>> {
|
||||
self.public_bindings(symbol)
|
||||
.next()
|
||||
.map(|constrained_binding| constrained_binding.binding)
|
||||
.find(Option::is_some)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn first_binding_at_use(&self, use_id: ScopedUseId) -> Option<Definition<'_>> {
|
||||
self.bindings_at_use(use_id)
|
||||
.next()
|
||||
.map(|constrained_binding| constrained_binding.binding)
|
||||
.find(Option::is_some)
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,15 +6,16 @@ use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use ruff_db::files::File;
|
||||
use ruff_db::parsed::ParsedModule;
|
||||
use ruff_index::IndexVec;
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::name::Name;
|
||||
use ruff_python_ast::visitor::{walk_expr, walk_pattern, walk_stmt, Visitor};
|
||||
use ruff_python_ast::{self as ast, Pattern};
|
||||
use ruff_python_ast::{BoolOp, Expr};
|
||||
|
||||
use crate::ast_node_ref::AstNodeRef;
|
||||
use crate::module_name::ModuleName;
|
||||
use crate::semantic_index::ast_ids::node_key::ExpressionNodeKey;
|
||||
use crate::semantic_index::ast_ids::AstIdsBuilder;
|
||||
use crate::semantic_index::constraint::PatternConstraintKind;
|
||||
use crate::semantic_index::definition::{
|
||||
AssignmentDefinitionNodeRef, ComprehensionDefinitionNodeRef, Definition, DefinitionNodeKey,
|
||||
DefinitionNodeRef, ForStmtDefinitionNodeRef, ImportFromDefinitionNodeRef,
|
||||
@@ -24,7 +25,10 @@ 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::{
|
||||
FlowSnapshot, ScopedConstraintId, ScopedVisibilityConstraintId, UseDefMapBuilder,
|
||||
};
|
||||
use crate::semantic_index::visibility_constraint::VisibilityConstraintRef;
|
||||
use crate::semantic_index::SemanticIndex;
|
||||
use crate::unpack::Unpack;
|
||||
use crate::Db;
|
||||
@@ -157,7 +161,7 @@ impl<'db> SemanticIndexBuilder<'db> {
|
||||
|
||||
let file_scope_id = self.scopes.push(scope);
|
||||
self.symbol_tables.push(SymbolTableBuilder::default());
|
||||
self.use_def_maps.push(UseDefMapBuilder::default());
|
||||
self.use_def_maps.push(UseDefMapBuilder::new());
|
||||
let ast_id_scope = self.ast_ids.push(AstIdsBuilder::default());
|
||||
|
||||
let scope_id = ScopeId::new(self.db, self.file, file_scope_id, countme::Count::default());
|
||||
@@ -279,14 +283,30 @@ impl<'db> SemanticIndexBuilder<'db> {
|
||||
definition
|
||||
}
|
||||
|
||||
fn record_expression_constraint(&mut self, constraint_node: &ast::Expr) -> Constraint<'db> {
|
||||
fn record_expression_constraint(
|
||||
&mut self,
|
||||
constraint_node: &ast::Expr,
|
||||
) -> (ScopedConstraintId, Constraint<'db>) {
|
||||
let constraint = self.build_constraint(constraint_node);
|
||||
self.record_constraint(constraint);
|
||||
constraint
|
||||
let constraint_id = self.record_constraint(constraint);
|
||||
(constraint_id, constraint)
|
||||
}
|
||||
|
||||
fn record_constraint(&mut self, constraint: Constraint<'db>) {
|
||||
self.current_use_def_map_mut().record_constraint(constraint);
|
||||
fn record_constraint(&mut self, constraint: Constraint<'db>) -> ScopedConstraintId {
|
||||
self.current_use_def_map_mut().record_constraint(constraint)
|
||||
}
|
||||
|
||||
fn record_visibility_constraint(
|
||||
&mut self,
|
||||
constraint: ScopedConstraintId,
|
||||
) -> ScopedVisibilityConstraintId {
|
||||
self.current_use_def_map_mut()
|
||||
.record_visibility_constraint(&VisibilityConstraintRef::Single(constraint))
|
||||
}
|
||||
|
||||
fn record_negated_visibility_constraint(&mut self, constraint: ScopedVisibilityConstraintId) {
|
||||
self.current_use_def_map_mut()
|
||||
.record_visibility_constraint(&VisibilityConstraintRef::Negated(constraint));
|
||||
}
|
||||
|
||||
fn build_constraint(&mut self, constraint_node: &Expr) -> Constraint<'db> {
|
||||
@@ -297,12 +317,13 @@ impl<'db> SemanticIndexBuilder<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
fn record_negated_constraint(&mut self, constraint: Constraint<'db>) {
|
||||
self.current_use_def_map_mut()
|
||||
.record_constraint(Constraint {
|
||||
node: constraint.node,
|
||||
is_positive: false,
|
||||
});
|
||||
fn record_negated_constraint(&mut self, constraint: Constraint<'db>) -> ScopedConstraintId {
|
||||
let negated = Constraint {
|
||||
node: constraint.node,
|
||||
is_positive: false,
|
||||
};
|
||||
let constraint_id = self.current_use_def_map_mut().record_constraint(negated);
|
||||
constraint_id
|
||||
}
|
||||
|
||||
fn push_assignment(&mut self, assignment: CurrentAssignment<'db>) {
|
||||
@@ -324,30 +345,31 @@ impl<'db> SemanticIndexBuilder<'db> {
|
||||
|
||||
fn add_pattern_constraint(
|
||||
&mut self,
|
||||
subject: &ast::Expr,
|
||||
subject: Expression<'db>,
|
||||
pattern: &ast::Pattern,
|
||||
) -> PatternConstraint<'db> {
|
||||
#[allow(unsafe_code)]
|
||||
let (subject, pattern) = unsafe {
|
||||
(
|
||||
AstNodeRef::new(self.module.clone(), subject),
|
||||
AstNodeRef::new(self.module.clone(), pattern),
|
||||
)
|
||||
) -> ScopedConstraintId {
|
||||
let kind = match pattern {
|
||||
Pattern::MatchValue(pattern) => {
|
||||
let value = self.add_standalone_expression(&pattern.value);
|
||||
PatternConstraintKind::Value(value)
|
||||
}
|
||||
Pattern::MatchSingleton(singleton) => PatternConstraintKind::Singleton(singleton.value),
|
||||
_ => PatternConstraintKind::Unsupported,
|
||||
};
|
||||
|
||||
let pattern_constraint = PatternConstraint::new(
|
||||
self.db,
|
||||
self.file,
|
||||
self.current_scope(),
|
||||
subject,
|
||||
pattern,
|
||||
kind,
|
||||
countme::Count::default(),
|
||||
);
|
||||
self.current_use_def_map_mut()
|
||||
.record_constraint(Constraint {
|
||||
node: ConstraintNode::Pattern(pattern_constraint),
|
||||
is_positive: true,
|
||||
});
|
||||
pattern_constraint
|
||||
})
|
||||
}
|
||||
|
||||
/// Record an expression that needs to be a Salsa ingredient, because we need to infer its type
|
||||
@@ -796,9 +818,13 @@ where
|
||||
ast::Stmt::If(node) => {
|
||||
self.visit_expr(&node.test);
|
||||
let pre_if = self.flow_snapshot();
|
||||
let constraint = self.record_expression_constraint(&node.test);
|
||||
let mut constraints = vec![constraint];
|
||||
let (constraint_id, constraint) = self.record_expression_constraint(&node.test);
|
||||
let mut constraints = vec![(constraint_id, constraint)];
|
||||
self.visit_body(&node.body);
|
||||
|
||||
let visibility_constraint_id = self.record_visibility_constraint(constraint_id);
|
||||
let mut vis_constraints = vec![visibility_constraint_id];
|
||||
|
||||
let mut post_clauses: Vec<FlowSnapshot> = vec![];
|
||||
let elif_else_clauses = node
|
||||
.elif_else_clauses
|
||||
@@ -822,15 +848,30 @@ where
|
||||
// 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());
|
||||
for constraint in &constraints {
|
||||
for (_, constraint) in &constraints {
|
||||
self.record_negated_constraint(*constraint);
|
||||
}
|
||||
if let Some(elif_test) = clause_test {
|
||||
|
||||
let elif_constraint = if let Some(elif_test) = clause_test {
|
||||
self.visit_expr(elif_test);
|
||||
constraints.push(self.record_expression_constraint(elif_test));
|
||||
}
|
||||
let (constraint_id, constraint) =
|
||||
self.record_expression_constraint(elif_test);
|
||||
constraints.push((constraint_id, constraint));
|
||||
Some(constraint_id)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
self.visit_body(clause_body);
|
||||
|
||||
for id in &vis_constraints {
|
||||
self.record_negated_visibility_constraint(*id);
|
||||
}
|
||||
if let Some(elif_constraint) = elif_constraint {
|
||||
let id = self.record_visibility_constraint(elif_constraint);
|
||||
vis_constraints.push(id);
|
||||
}
|
||||
}
|
||||
|
||||
for post_clause_state in post_clauses {
|
||||
self.flow_merge(post_clause_state);
|
||||
}
|
||||
@@ -844,7 +885,7 @@ where
|
||||
self.visit_expr(test);
|
||||
|
||||
let pre_loop = self.flow_snapshot();
|
||||
let constraint = self.record_expression_constraint(test);
|
||||
let (constraint_id, constraint) = self.record_expression_constraint(test);
|
||||
|
||||
// Save aside any break states from an outer loop
|
||||
let saved_break_states = std::mem::take(&mut self.loop_break_states);
|
||||
@@ -861,12 +902,16 @@ where
|
||||
let break_states =
|
||||
std::mem::replace(&mut self.loop_break_states, saved_break_states);
|
||||
|
||||
let vis_constraint_id = self.record_visibility_constraint(constraint_id);
|
||||
|
||||
// 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.record_negated_constraint(constraint);
|
||||
self.visit_body(orelse);
|
||||
|
||||
self.record_negated_visibility_constraint(vis_constraint_id);
|
||||
|
||||
// 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 {
|
||||
@@ -947,22 +992,34 @@ where
|
||||
cases,
|
||||
range: _,
|
||||
}) => {
|
||||
self.add_standalone_expression(subject);
|
||||
let subject_expr = self.add_standalone_expression(subject);
|
||||
self.visit_expr(subject);
|
||||
|
||||
let after_subject = self.flow_snapshot();
|
||||
let Some((first, remaining)) = cases.split_first() else {
|
||||
return;
|
||||
};
|
||||
self.add_pattern_constraint(subject, &first.pattern);
|
||||
|
||||
let first_constraint_id = self.add_pattern_constraint(subject_expr, &first.pattern);
|
||||
|
||||
self.visit_match_case(first);
|
||||
|
||||
let first_vis_constraint_id =
|
||||
self.record_visibility_constraint(first_constraint_id);
|
||||
let mut vis_constraints = vec![first_vis_constraint_id];
|
||||
|
||||
let mut post_case_snapshots = vec![];
|
||||
for case in remaining {
|
||||
post_case_snapshots.push(self.flow_snapshot());
|
||||
self.flow_restore(after_subject.clone());
|
||||
self.add_pattern_constraint(subject, &case.pattern);
|
||||
let constraint_id = self.add_pattern_constraint(subject_expr, &case.pattern);
|
||||
self.visit_match_case(case);
|
||||
|
||||
for id in &vis_constraints {
|
||||
self.record_negated_visibility_constraint(*id);
|
||||
}
|
||||
let vis_constraint_id = self.record_visibility_constraint(constraint_id);
|
||||
vis_constraints.push(vis_constraint_id);
|
||||
}
|
||||
for post_clause_state in post_case_snapshots {
|
||||
self.flow_merge(post_clause_state);
|
||||
@@ -972,6 +1029,14 @@ where
|
||||
.is_some_and(|case| case.guard.is_none() && case.pattern.is_wildcard())
|
||||
{
|
||||
self.flow_merge(after_subject);
|
||||
|
||||
// for post_clause_state in post_case_snapshots {
|
||||
// self.flow_merge(post_clause_state);
|
||||
// }
|
||||
|
||||
for id in &vis_constraints {
|
||||
self.record_negated_visibility_constraint(*id);
|
||||
}
|
||||
}
|
||||
}
|
||||
ast::Stmt::Try(ast::StmtTry {
|
||||
@@ -1222,12 +1287,9 @@ 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 constraint = self.record_expression_constraint(test);
|
||||
let (_, constraint) = self.record_expression_constraint(test);
|
||||
self.visit_expr(body);
|
||||
let post_body = self.flow_snapshot();
|
||||
self.flow_restore(pre_if);
|
||||
@@ -1291,22 +1353,22 @@ where
|
||||
range: _,
|
||||
op,
|
||||
}) => {
|
||||
// TODO detect statically known truthy or falsy values (via type inference, not naive
|
||||
// AST inspection, so we can't simplify here, need to record test expression for
|
||||
// later checking)
|
||||
let mut snapshots = vec![];
|
||||
|
||||
let mut last_constraint_id = None;
|
||||
for (index, value) in values.iter().enumerate() {
|
||||
self.visit_expr(value);
|
||||
if let Some(last_constraint_id) = last_constraint_id {
|
||||
self.record_visibility_constraint(last_constraint_id);
|
||||
}
|
||||
// In the last value we don't need to take a snapshot nor add a constraint
|
||||
if index < values.len() - 1 {
|
||||
// Snapshot is taken after visiting the expression but before adding the constraint.
|
||||
snapshots.push(self.flow_snapshot());
|
||||
let constraint = self.build_constraint(value);
|
||||
match op {
|
||||
last_constraint_id = Some(match op {
|
||||
BoolOp::And => self.record_constraint(constraint),
|
||||
BoolOp::Or => self.record_negated_constraint(constraint),
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
for snapshot in snapshots {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use ruff_db::files::File;
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::Singleton;
|
||||
|
||||
use crate::ast_node_ref::AstNodeRef;
|
||||
use crate::db::Db;
|
||||
use crate::semantic_index::expression::Expression;
|
||||
use crate::semantic_index::symbol::{FileScopeId, ScopeId};
|
||||
@@ -18,6 +17,14 @@ pub(crate) enum ConstraintNode<'db> {
|
||||
Pattern(PatternConstraint<'db>),
|
||||
}
|
||||
|
||||
/// Pattern kinds for which we do support type narrowing and/or static truthiness analysis.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub(crate) enum PatternConstraintKind<'db> {
|
||||
Singleton(Singleton),
|
||||
Value(Expression<'db>),
|
||||
Unsupported,
|
||||
}
|
||||
|
||||
#[salsa::tracked]
|
||||
pub(crate) struct PatternConstraint<'db> {
|
||||
#[id]
|
||||
@@ -28,11 +35,11 @@ pub(crate) struct PatternConstraint<'db> {
|
||||
|
||||
#[no_eq]
|
||||
#[return_ref]
|
||||
pub(crate) subject: AstNodeRef<ast::Expr>,
|
||||
pub(crate) subject: Expression<'db>,
|
||||
|
||||
#[no_eq]
|
||||
#[return_ref]
|
||||
pub(crate) pattern: AstNodeRef<ast::Pattern>,
|
||||
pub(crate) kind: PatternConstraintKind<'db>,
|
||||
|
||||
#[no_eq]
|
||||
count: countme::Count<PatternConstraint<'static>>,
|
||||
|
||||
@@ -223,12 +223,13 @@
|
||||
//! visits a `StmtIf` node.
|
||||
use self::symbol_state::{
|
||||
BindingIdWithConstraintsIterator, ConstraintIdIterator, DeclarationIdIterator,
|
||||
ScopedConstraintId, ScopedDefinitionId, SymbolBindings, SymbolDeclarations, SymbolState,
|
||||
ScopedDefinitionId, SymbolBindings, SymbolDeclarations, SymbolState,
|
||||
};
|
||||
pub(crate) use self::symbol_state::{ScopedConstraintId, ScopedVisibilityConstraintId};
|
||||
use crate::semantic_index::ast_ids::ScopedUseId;
|
||||
use crate::semantic_index::definition::Definition;
|
||||
use crate::semantic_index::symbol::ScopedSymbolId;
|
||||
use crate::symbol::Boundness;
|
||||
pub(crate) use crate::semantic_index::visibility_constraint::VisibilityConstraintRef;
|
||||
use ruff_index::IndexVec;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
@@ -241,11 +242,14 @@ mod symbol_state;
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub(crate) struct UseDefMap<'db> {
|
||||
/// Array of [`Definition`] in this scope.
|
||||
all_definitions: IndexVec<ScopedDefinitionId, Definition<'db>>,
|
||||
all_definitions: IndexVec<ScopedDefinitionId, Option<Definition<'db>>>,
|
||||
|
||||
/// Array of [`Constraint`] in this scope.
|
||||
all_constraints: IndexVec<ScopedConstraintId, Constraint<'db>>,
|
||||
|
||||
/// Array of [`VisibilityConstraintRef`] in this scope.
|
||||
all_visibility_constraints: IndexVec<ScopedVisibilityConstraintId, VisibilityConstraintRef>,
|
||||
|
||||
/// [`SymbolBindings`] reaching a [`ScopedUseId`].
|
||||
bindings_by_use: IndexVec<ScopedUseId, SymbolBindings>,
|
||||
|
||||
@@ -275,14 +279,6 @@ impl<'db> UseDefMap<'db> {
|
||||
self.bindings_iterator(&self.bindings_by_use[use_id])
|
||||
}
|
||||
|
||||
pub(crate) fn use_boundness(&self, use_id: ScopedUseId) -> Boundness {
|
||||
if self.bindings_by_use[use_id].may_be_unbound() {
|
||||
Boundness::PossiblyUnbound
|
||||
} else {
|
||||
Boundness::Bound
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn public_bindings(
|
||||
&self,
|
||||
symbol: ScopedSymbolId,
|
||||
@@ -290,14 +286,6 @@ impl<'db> UseDefMap<'db> {
|
||||
self.bindings_iterator(self.public_symbols[symbol].bindings())
|
||||
}
|
||||
|
||||
pub(crate) fn public_boundness(&self, symbol: ScopedSymbolId) -> Boundness {
|
||||
if self.public_symbols[symbol].may_be_unbound() {
|
||||
Boundness::PossiblyUnbound
|
||||
} else {
|
||||
Boundness::Bound
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn bindings_at_declaration(
|
||||
&self,
|
||||
declaration: Definition<'db>,
|
||||
@@ -310,10 +298,10 @@ impl<'db> UseDefMap<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn declarations_at_binding(
|
||||
&self,
|
||||
pub(crate) fn declarations_at_binding<'map>(
|
||||
&'map self,
|
||||
binding: Definition<'db>,
|
||||
) -> DeclarationsIterator<'_, 'db> {
|
||||
) -> DeclarationsIterator<'map, 'db> {
|
||||
if let SymbolDefinitions::Declarations(declarations) =
|
||||
&self.definitions_by_definition[&binding]
|
||||
{
|
||||
@@ -323,37 +311,39 @@ impl<'db> UseDefMap<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn public_declarations(
|
||||
&self,
|
||||
pub(crate) fn public_declarations<'map>(
|
||||
&'map self,
|
||||
symbol: ScopedSymbolId,
|
||||
) -> DeclarationsIterator<'_, 'db> {
|
||||
) -> DeclarationsIterator<'map, 'db> {
|
||||
let declarations = self.public_symbols[symbol].declarations();
|
||||
self.declarations_iterator(declarations)
|
||||
}
|
||||
|
||||
pub(crate) fn has_public_declarations(&self, symbol: ScopedSymbolId) -> bool {
|
||||
!self.public_symbols[symbol].declarations().is_empty()
|
||||
}
|
||||
|
||||
fn bindings_iterator<'a>(
|
||||
&'a self,
|
||||
bindings: &'a SymbolBindings,
|
||||
) -> BindingWithConstraintsIterator<'a, 'db> {
|
||||
fn bindings_iterator<'map>(
|
||||
&'map self,
|
||||
bindings: &'map SymbolBindings,
|
||||
) -> BindingWithConstraintsIterator<'map, 'db> {
|
||||
BindingWithConstraintsIterator {
|
||||
all_definitions: &self.all_definitions,
|
||||
all_constraints: &self.all_constraints,
|
||||
inner: bindings.iter(),
|
||||
inner: bindings.iter(&self.all_constraints, &self.all_visibility_constraints),
|
||||
}
|
||||
}
|
||||
|
||||
fn declarations_iterator<'a>(
|
||||
&'a self,
|
||||
declarations: &'a SymbolDeclarations,
|
||||
) -> DeclarationsIterator<'a, 'db> {
|
||||
fn declarations_iterator<'map>(
|
||||
&'map self,
|
||||
declarations: &'map SymbolDeclarations,
|
||||
) -> DeclarationsIterator<'map, 'db> {
|
||||
DeclarationsIterator {
|
||||
all_definitions: &self.all_definitions,
|
||||
inner: declarations.iter(),
|
||||
may_be_undeclared: declarations.may_be_undeclared(),
|
||||
inner: {
|
||||
DeclarationIdIterator {
|
||||
all_constraints: &self.all_constraints,
|
||||
all_visibility_constraints: &self.all_visibility_constraints,
|
||||
inner: declarations.live_declarations.iter(),
|
||||
visibility_constraints: declarations.visibility_constraints.iter(),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -367,23 +357,28 @@ enum SymbolDefinitions {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct BindingWithConstraintsIterator<'map, 'db> {
|
||||
all_definitions: &'map IndexVec<ScopedDefinitionId, Definition<'db>>,
|
||||
all_definitions: &'map IndexVec<ScopedDefinitionId, Option<Definition<'db>>>,
|
||||
all_constraints: &'map IndexVec<ScopedConstraintId, Constraint<'db>>,
|
||||
inner: BindingIdWithConstraintsIterator<'map>,
|
||||
inner: BindingIdWithConstraintsIterator<'map, 'db>,
|
||||
}
|
||||
|
||||
impl<'map, 'db> Iterator for BindingWithConstraintsIterator<'map, 'db> {
|
||||
type Item = BindingWithConstraints<'map, 'db>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let all_constraints = self.all_constraints;
|
||||
|
||||
self.inner
|
||||
.next()
|
||||
.map(|def_id_with_constraints| BindingWithConstraints {
|
||||
binding: self.all_definitions[def_id_with_constraints.definition],
|
||||
.map(|binding_id_with_constraints| BindingWithConstraints {
|
||||
binding: self.all_definitions[binding_id_with_constraints.definition],
|
||||
constraints: ConstraintsIterator {
|
||||
all_constraints: self.all_constraints,
|
||||
constraint_ids: def_id_with_constraints.constraint_ids,
|
||||
all_constraints,
|
||||
constraint_ids: binding_id_with_constraints.constraint_ids,
|
||||
},
|
||||
all_constraints: binding_id_with_constraints.all_constraints,
|
||||
all_visibility_constraints: binding_id_with_constraints.all_visibility_constraints,
|
||||
visibility_constraint: binding_id_with_constraints.visibility_constraint,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -391,8 +386,12 @@ impl<'map, 'db> Iterator for BindingWithConstraintsIterator<'map, 'db> {
|
||||
impl std::iter::FusedIterator for BindingWithConstraintsIterator<'_, '_> {}
|
||||
|
||||
pub(crate) struct BindingWithConstraints<'map, 'db> {
|
||||
pub(crate) binding: Definition<'db>,
|
||||
pub(crate) binding: Option<Definition<'db>>,
|
||||
pub(crate) constraints: ConstraintsIterator<'map, 'db>,
|
||||
pub(crate) all_constraints: &'map IndexVec<ScopedConstraintId, Constraint<'db>>,
|
||||
pub(crate) all_visibility_constraints:
|
||||
&'map IndexVec<ScopedVisibilityConstraintId, VisibilityConstraintRef>,
|
||||
pub(crate) visibility_constraint: ScopedVisibilityConstraintId,
|
||||
}
|
||||
|
||||
pub(crate) struct ConstraintsIterator<'map, 'db> {
|
||||
@@ -413,22 +412,29 @@ impl<'db> Iterator for ConstraintsIterator<'_, 'db> {
|
||||
impl std::iter::FusedIterator for ConstraintsIterator<'_, '_> {}
|
||||
|
||||
pub(crate) struct DeclarationsIterator<'map, 'db> {
|
||||
all_definitions: &'map IndexVec<ScopedDefinitionId, Definition<'db>>,
|
||||
inner: DeclarationIdIterator<'map>,
|
||||
may_be_undeclared: bool,
|
||||
all_definitions: &'map IndexVec<ScopedDefinitionId, Option<Definition<'db>>>,
|
||||
inner: DeclarationIdIterator<'map, 'db>,
|
||||
}
|
||||
|
||||
impl DeclarationsIterator<'_, '_> {
|
||||
pub(crate) fn may_be_undeclared(&self) -> bool {
|
||||
self.may_be_undeclared
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> Iterator for DeclarationsIterator<'_, 'db> {
|
||||
type Item = Definition<'db>;
|
||||
impl<'map, 'db> Iterator for DeclarationsIterator<'map, 'db> {
|
||||
type Item = (
|
||||
Option<Definition<'db>>,
|
||||
&'map IndexVec<ScopedConstraintId, Constraint<'db>>,
|
||||
&'map IndexVec<ScopedVisibilityConstraintId, VisibilityConstraintRef>,
|
||||
ScopedVisibilityConstraintId,
|
||||
);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.inner.next().map(|def_id| self.all_definitions[def_id])
|
||||
self.inner.next().map(
|
||||
|(def_id, all_constraints, all_visibility_constraints, visibility_constraint_id)| {
|
||||
(
|
||||
self.all_definitions[def_id],
|
||||
all_constraints,
|
||||
all_visibility_constraints,
|
||||
visibility_constraint_id,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -438,16 +444,22 @@ impl std::iter::FusedIterator for DeclarationsIterator<'_, '_> {}
|
||||
#[derive(Clone, Debug)]
|
||||
pub(super) struct FlowSnapshot {
|
||||
symbol_states: IndexVec<ScopedSymbolId, SymbolState>,
|
||||
unbound_visibility_constraint_id: ScopedVisibilityConstraintId,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug)]
|
||||
pub(super) struct UseDefMapBuilder<'db> {
|
||||
/// Append-only array of [`Definition`].
|
||||
all_definitions: IndexVec<ScopedDefinitionId, Definition<'db>>,
|
||||
all_definitions: IndexVec<ScopedDefinitionId, Option<Definition<'db>>>,
|
||||
|
||||
/// Append-only array of [`Constraint`].
|
||||
all_constraints: IndexVec<ScopedConstraintId, Constraint<'db>>,
|
||||
|
||||
/// Append-only array of [`VisibilityConstraintRef`].
|
||||
all_visibility_constraints: IndexVec<ScopedVisibilityConstraintId, VisibilityConstraintRef>,
|
||||
|
||||
unbound_visibility_constraint_id: ScopedVisibilityConstraintId,
|
||||
|
||||
/// Live bindings at each so-far-recorded use.
|
||||
bindings_by_use: IndexVec<ScopedUseId, SymbolBindings>,
|
||||
|
||||
@@ -459,13 +471,27 @@ pub(super) struct UseDefMapBuilder<'db> {
|
||||
}
|
||||
|
||||
impl<'db> UseDefMapBuilder<'db> {
|
||||
pub(super) fn new() -> Self {
|
||||
Self {
|
||||
all_definitions: IndexVec::from_iter([None]),
|
||||
all_constraints: IndexVec::new(),
|
||||
all_visibility_constraints: IndexVec::from_iter([VisibilityConstraintRef::None]),
|
||||
unbound_visibility_constraint_id: ScopedVisibilityConstraintId::from_u32(0),
|
||||
bindings_by_use: IndexVec::new(),
|
||||
definitions_by_definition: FxHashMap::default(),
|
||||
symbol_states: IndexVec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn add_symbol(&mut self, symbol: ScopedSymbolId) {
|
||||
let new_symbol = self.symbol_states.push(SymbolState::undefined());
|
||||
let new_symbol = self.symbol_states.push(SymbolState::undefined(
|
||||
self.unbound_visibility_constraint_id,
|
||||
));
|
||||
debug_assert_eq!(symbol, new_symbol);
|
||||
}
|
||||
|
||||
pub(super) fn record_binding(&mut self, symbol: ScopedSymbolId, binding: Definition<'db>) {
|
||||
let def_id = self.all_definitions.push(binding);
|
||||
let def_id = self.all_definitions.push(Some(binding));
|
||||
let symbol_state = &mut self.symbol_states[symbol];
|
||||
self.definitions_by_definition.insert(
|
||||
binding,
|
||||
@@ -474,11 +500,38 @@ impl<'db> UseDefMapBuilder<'db> {
|
||||
symbol_state.record_binding(def_id);
|
||||
}
|
||||
|
||||
pub(super) fn record_constraint(&mut self, constraint: Constraint<'db>) {
|
||||
pub(super) fn record_constraint(&mut self, constraint: Constraint<'db>) -> ScopedConstraintId {
|
||||
let constraint_id = self.all_constraints.push(constraint);
|
||||
for state in &mut self.symbol_states {
|
||||
state.record_constraint(constraint_id);
|
||||
}
|
||||
constraint_id
|
||||
}
|
||||
|
||||
pub(super) fn record_visibility_constraint(
|
||||
&mut self,
|
||||
constraint: &VisibilityConstraintRef,
|
||||
) -> ScopedVisibilityConstraintId {
|
||||
let new_constraint_id = self.all_visibility_constraints.push(constraint.clone());
|
||||
for state in &mut self.symbol_states {
|
||||
state.record_visibility_constraint(
|
||||
&mut self.all_visibility_constraints,
|
||||
new_constraint_id,
|
||||
);
|
||||
}
|
||||
|
||||
if self.unbound_visibility_constraint_id == ScopedVisibilityConstraintId::from_u32(0) {
|
||||
self.unbound_visibility_constraint_id = new_constraint_id;
|
||||
} else {
|
||||
self.unbound_visibility_constraint_id =
|
||||
self.all_visibility_constraints
|
||||
.push(VisibilityConstraintRef::And(
|
||||
self.unbound_visibility_constraint_id,
|
||||
new_constraint_id,
|
||||
));
|
||||
}
|
||||
|
||||
new_constraint_id
|
||||
}
|
||||
|
||||
pub(super) fn record_declaration(
|
||||
@@ -486,7 +539,7 @@ impl<'db> UseDefMapBuilder<'db> {
|
||||
symbol: ScopedSymbolId,
|
||||
declaration: Definition<'db>,
|
||||
) {
|
||||
let def_id = self.all_definitions.push(declaration);
|
||||
let def_id = self.all_definitions.push(Some(declaration));
|
||||
let symbol_state = &mut self.symbol_states[symbol];
|
||||
self.definitions_by_definition.insert(
|
||||
declaration,
|
||||
@@ -501,7 +554,7 @@ impl<'db> UseDefMapBuilder<'db> {
|
||||
definition: Definition<'db>,
|
||||
) {
|
||||
// We don't need to store anything in self.definitions_by_definition.
|
||||
let def_id = self.all_definitions.push(definition);
|
||||
let def_id = self.all_definitions.push(Some(definition));
|
||||
let symbol_state = &mut self.symbol_states[symbol];
|
||||
symbol_state.record_declaration(def_id);
|
||||
symbol_state.record_binding(def_id);
|
||||
@@ -520,6 +573,7 @@ impl<'db> UseDefMapBuilder<'db> {
|
||||
pub(super) fn snapshot(&self) -> FlowSnapshot {
|
||||
FlowSnapshot {
|
||||
symbol_states: self.symbol_states.clone(),
|
||||
unbound_visibility_constraint_id: self.unbound_visibility_constraint_id,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -533,12 +587,15 @@ impl<'db> UseDefMapBuilder<'db> {
|
||||
|
||||
// Restore the current visible-definitions state to the given snapshot.
|
||||
self.symbol_states = snapshot.symbol_states;
|
||||
self.unbound_visibility_constraint_id = snapshot.unbound_visibility_constraint_id;
|
||||
|
||||
// If the snapshot we are restoring is missing some symbols we've recorded since, we need
|
||||
// to fill them in so the symbol IDs continue to line up. Since they don't exist in the
|
||||
// snapshot, the correct state to fill them in with is "undefined".
|
||||
self.symbol_states
|
||||
.resize(num_symbols, SymbolState::undefined());
|
||||
self.symbol_states.resize(
|
||||
num_symbols,
|
||||
SymbolState::undefined(self.unbound_visibility_constraint_id),
|
||||
);
|
||||
}
|
||||
|
||||
/// Merge the given snapshot into the current state, reflecting that we might have taken either
|
||||
@@ -553,11 +610,40 @@ impl<'db> UseDefMapBuilder<'db> {
|
||||
let mut snapshot_definitions_iter = snapshot.symbol_states.into_iter();
|
||||
for current in &mut self.symbol_states {
|
||||
if let Some(snapshot) = snapshot_definitions_iter.next() {
|
||||
current.merge(snapshot);
|
||||
current.merge(snapshot, &mut self.all_visibility_constraints);
|
||||
} else {
|
||||
current.merge(
|
||||
SymbolState::undefined(snapshot.unbound_visibility_constraint_id),
|
||||
&mut self.all_visibility_constraints,
|
||||
);
|
||||
// Symbol not present in snapshot, so it's unbound/undeclared from that path.
|
||||
current.set_may_be_unbound();
|
||||
current.set_may_be_undeclared();
|
||||
}
|
||||
}
|
||||
|
||||
// Merge unbound visibility constraints:
|
||||
match (
|
||||
&self.all_visibility_constraints[self.unbound_visibility_constraint_id],
|
||||
&self.all_visibility_constraints[snapshot.unbound_visibility_constraint_id],
|
||||
) {
|
||||
(_, VisibilityConstraintRef::Negated(id))
|
||||
if self.unbound_visibility_constraint_id == *id =>
|
||||
{
|
||||
self.unbound_visibility_constraint_id = ScopedVisibilityConstraintId::from_u32(0);
|
||||
}
|
||||
|
||||
(VisibilityConstraintRef::Negated(id), _)
|
||||
if *id == snapshot.unbound_visibility_constraint_id =>
|
||||
{
|
||||
self.unbound_visibility_constraint_id = ScopedVisibilityConstraintId::from_u32(0);
|
||||
}
|
||||
_ => {
|
||||
let constraint_id =
|
||||
self.all_visibility_constraints
|
||||
.push(VisibilityConstraintRef::Or(
|
||||
self.unbound_visibility_constraint_id,
|
||||
snapshot.unbound_visibility_constraint_id,
|
||||
));
|
||||
self.unbound_visibility_constraint_id = constraint_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -572,6 +658,7 @@ impl<'db> UseDefMapBuilder<'db> {
|
||||
UseDefMap {
|
||||
all_definitions: self.all_definitions,
|
||||
all_constraints: self.all_constraints,
|
||||
all_visibility_constraints: self.all_visibility_constraints,
|
||||
bindings_by_use: self.bindings_by_use,
|
||||
public_symbols: self.symbol_states,
|
||||
definitions_by_definition: self.definitions_by_definition,
|
||||
|
||||
@@ -32,10 +32,6 @@ impl<const B: usize> BitSet<B> {
|
||||
bitset
|
||||
}
|
||||
|
||||
pub(super) fn is_empty(&self) -> bool {
|
||||
self.blocks().iter().all(|&b| b == 0)
|
||||
}
|
||||
|
||||
/// Convert from Inline to Heap, if needed, and resize the Heap vector, if needed.
|
||||
fn resize(&mut self, value: u32) {
|
||||
let num_blocks_needed = (value / 64) + 1;
|
||||
@@ -97,19 +93,6 @@ impl<const B: usize> BitSet<B> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Union in-place with another [`BitSet`].
|
||||
pub(super) fn union(&mut self, other: &BitSet<B>) {
|
||||
let mut max_len = self.blocks().len();
|
||||
let other_len = other.blocks().len();
|
||||
if other_len > max_len {
|
||||
max_len = other_len;
|
||||
self.resize_blocks(max_len);
|
||||
}
|
||||
for (my_block, other_block) in self.blocks_mut().iter_mut().zip(other.blocks()) {
|
||||
*my_block |= other_block;
|
||||
}
|
||||
}
|
||||
|
||||
/// Return an iterator over the values (in ascending order) in this [`BitSet`].
|
||||
pub(super) fn iter(&self) -> BitSetIterator<'_, B> {
|
||||
let blocks = self.blocks();
|
||||
@@ -239,59 +222,6 @@ mod tests {
|
||||
assert_bitset(&b1, &[89]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn union() {
|
||||
let mut b1 = BitSet::<1>::with(2);
|
||||
let b2 = BitSet::<1>::with(4);
|
||||
|
||||
b1.union(&b2);
|
||||
assert_bitset(&b1, &[2, 4]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn union_mixed_1() {
|
||||
let mut b1 = BitSet::<1>::with(4);
|
||||
let mut b2 = BitSet::<1>::with(4);
|
||||
b1.insert(89);
|
||||
b2.insert(5);
|
||||
|
||||
b1.union(&b2);
|
||||
assert_bitset(&b1, &[4, 5, 89]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn union_mixed_2() {
|
||||
let mut b1 = BitSet::<1>::with(4);
|
||||
let mut b2 = BitSet::<1>::with(4);
|
||||
b1.insert(23);
|
||||
b2.insert(89);
|
||||
|
||||
b1.union(&b2);
|
||||
assert_bitset(&b1, &[4, 23, 89]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn union_heap() {
|
||||
let mut b1 = BitSet::<1>::with(4);
|
||||
let mut b2 = BitSet::<1>::with(4);
|
||||
b1.insert(89);
|
||||
b2.insert(90);
|
||||
|
||||
b1.union(&b2);
|
||||
assert_bitset(&b1, &[4, 89, 90]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn union_heap_2() {
|
||||
let mut b1 = BitSet::<1>::with(89);
|
||||
let mut b2 = BitSet::<1>::with(89);
|
||||
b1.insert(91);
|
||||
b2.insert(90);
|
||||
|
||||
b1.union(&b2);
|
||||
assert_bitset(&b1, &[89, 90, 91]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_blocks() {
|
||||
let mut b = BitSet::<2>::with(120);
|
||||
@@ -299,11 +229,4 @@ mod tests {
|
||||
assert!(matches!(b, BitSet::Inline(_)));
|
||||
assert_bitset(&b, &[45, 120]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty() {
|
||||
let b = BitSet::<1>::default();
|
||||
|
||||
assert!(b.is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,8 +43,10 @@
|
||||
//!
|
||||
//! Tracking live declarations is simpler, since constraints are not involved, but otherwise very
|
||||
//! similar to tracking live bindings.
|
||||
use crate::semantic_index::use_def::{Constraint, VisibilityConstraintRef};
|
||||
|
||||
use super::bitset::{BitSet, BitSetIterator};
|
||||
use ruff_index::newtype_index;
|
||||
use ruff_index::{newtype_index, IndexVec};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
/// A newtype-index for a definition in a particular scope.
|
||||
@@ -53,7 +55,7 @@ pub(super) struct ScopedDefinitionId;
|
||||
|
||||
/// A newtype-index for a constraint expression in a particular scope.
|
||||
#[newtype_index]
|
||||
pub(super) struct ScopedConstraintId;
|
||||
pub(crate) struct ScopedConstraintId;
|
||||
|
||||
/// Can reference this * 64 total definitions inline; more will fall back to the heap.
|
||||
const INLINE_BINDING_BLOCKS: usize = 3;
|
||||
@@ -75,55 +77,91 @@ const INLINE_CONSTRAINT_BLOCKS: usize = 2;
|
||||
/// Can keep inline this many live bindings per symbol at a given time; more will go to heap.
|
||||
const INLINE_BINDINGS_PER_SYMBOL: usize = 4;
|
||||
|
||||
/// One [`BitSet`] of applicable [`ScopedConstraintId`] per live binding.
|
||||
type InlineConstraintArray = [BitSet<INLINE_CONSTRAINT_BLOCKS>; INLINE_BINDINGS_PER_SYMBOL];
|
||||
type Constraints = SmallVec<InlineConstraintArray>;
|
||||
type ConstraintsIterator<'a> = std::slice::Iter<'a, BitSet<INLINE_CONSTRAINT_BLOCKS>>;
|
||||
/// Which constraints apply to a given binding?
|
||||
type Constraints = BitSet<INLINE_CONSTRAINT_BLOCKS>;
|
||||
|
||||
type InlineConstraintArray = [Constraints; INLINE_BINDINGS_PER_SYMBOL];
|
||||
|
||||
/// One [`BitSet`] of applicable [`ScopedConstraintId`]s per live binding.
|
||||
type ConstraintsPerBinding = SmallVec<InlineConstraintArray>;
|
||||
|
||||
/// Iterate over all constraints for a single binding.
|
||||
type ConstraintsIterator<'a> = std::slice::Iter<'a, Constraints>;
|
||||
type ConstraintsIntoIterator = smallvec::IntoIter<InlineConstraintArray>;
|
||||
|
||||
/// Similar to what we have above, but for visibility constraints.
|
||||
#[newtype_index]
|
||||
pub(crate) struct ScopedVisibilityConstraintId;
|
||||
const INLINE_VISIBILITY_CONSTRAINTS: usize = 4;
|
||||
type InlineVisibilityConstraintsArray =
|
||||
[ScopedVisibilityConstraintId; INLINE_VISIBILITY_CONSTRAINTS];
|
||||
type VisibilityConstraintPerBinding = SmallVec<InlineVisibilityConstraintsArray>;
|
||||
type VisibilityConstraintsIterator<'a> = std::slice::Iter<'a, ScopedVisibilityConstraintId>;
|
||||
type VisibilityConstraintsIntoIterator = smallvec::IntoIter<InlineVisibilityConstraintsArray>;
|
||||
|
||||
/// Live declarations for a single symbol at some point in control flow.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub(super) struct SymbolDeclarations {
|
||||
/// [`BitSet`]: which declarations (as [`ScopedDefinitionId`]) can reach the current location?
|
||||
live_declarations: Declarations,
|
||||
pub(crate) live_declarations: Declarations,
|
||||
|
||||
/// Could the symbol be un-declared at this point?
|
||||
may_be_undeclared: bool,
|
||||
/// For each live declaration, which [`VisibilityConstraints`] were active at that declaration?
|
||||
pub(crate) visibility_constraints: VisibilityConstraintPerBinding,
|
||||
}
|
||||
|
||||
impl SymbolDeclarations {
|
||||
fn undeclared() -> Self {
|
||||
fn undeclared(unbound_visibility_constraint_id: ScopedVisibilityConstraintId) -> Self {
|
||||
Self {
|
||||
live_declarations: Declarations::default(),
|
||||
may_be_undeclared: true,
|
||||
live_declarations: Declarations::with(0),
|
||||
visibility_constraints: VisibilityConstraintPerBinding::from_iter([
|
||||
unbound_visibility_constraint_id,
|
||||
]),
|
||||
}
|
||||
}
|
||||
|
||||
/// Record a newly-encountered declaration for this symbol.
|
||||
fn record_declaration(&mut self, declaration_id: ScopedDefinitionId) {
|
||||
self.live_declarations = Declarations::with(declaration_id.into());
|
||||
self.may_be_undeclared = false;
|
||||
|
||||
self.visibility_constraints = VisibilityConstraintPerBinding::with_capacity(1);
|
||||
self.visibility_constraints
|
||||
.push(ScopedVisibilityConstraintId::from_u32(0));
|
||||
}
|
||||
|
||||
/// Add undeclared as a possibility for this symbol.
|
||||
fn set_may_be_undeclared(&mut self) {
|
||||
self.may_be_undeclared = true;
|
||||
}
|
||||
|
||||
/// Return an iterator over live declarations for this symbol.
|
||||
pub(super) fn iter(&self) -> DeclarationIdIterator {
|
||||
DeclarationIdIterator {
|
||||
inner: self.live_declarations.iter(),
|
||||
/// Add given visibility constraint to all live bindings.
|
||||
pub(super) fn record_visibility_constraint(
|
||||
&mut self,
|
||||
all_visibility_constraints: &mut IndexVec<
|
||||
ScopedVisibilityConstraintId,
|
||||
VisibilityConstraintRef,
|
||||
>,
|
||||
constraint: ScopedVisibilityConstraintId,
|
||||
) {
|
||||
for existing in &mut self.visibility_constraints {
|
||||
if existing == &ScopedVisibilityConstraintId::from_u32(0) {
|
||||
*existing = constraint;
|
||||
} else {
|
||||
*existing = all_visibility_constraints
|
||||
.push(VisibilityConstraintRef::And(*existing, constraint));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn is_empty(&self) -> bool {
|
||||
self.live_declarations.is_empty()
|
||||
}
|
||||
// /// Return an iterator over live declarations for this symbol.
|
||||
// pub(super) fn iter<'map, 'db>(
|
||||
// &'db self,
|
||||
// all_constraints: &'map IndexVec<ScopedConstraintId, Constraint<'db>>,
|
||||
// ) -> DeclarationIdIterator<'map, 'db> {
|
||||
// DeclarationIdIterator {
|
||||
// all_constraints,
|
||||
// inner: self.live_declarations.iter(),
|
||||
// visibility_constraints: self.visibility_constraints.iter(),
|
||||
// }
|
||||
// }
|
||||
|
||||
pub(super) fn may_be_undeclared(&self) -> bool {
|
||||
self.may_be_undeclared
|
||||
}
|
||||
// pub(super) fn is_empty(&self) -> bool {
|
||||
// self.live_declarations.is_empty()
|
||||
// }
|
||||
}
|
||||
|
||||
/// Live bindings and narrowing constraints for a single symbol at some point in control flow.
|
||||
@@ -136,34 +174,34 @@ pub(super) struct SymbolBindings {
|
||||
///
|
||||
/// This is a [`smallvec::SmallVec`] which should always have one [`BitSet`] of constraints per
|
||||
/// binding in `live_bindings`.
|
||||
constraints: Constraints,
|
||||
constraints: ConstraintsPerBinding,
|
||||
|
||||
/// Could the symbol be unbound at this point?
|
||||
may_be_unbound: bool,
|
||||
/// For each live binding, which [`VisibilityConstraints`] were active at that binding?
|
||||
visibility_constraints: VisibilityConstraintPerBinding,
|
||||
}
|
||||
|
||||
impl SymbolBindings {
|
||||
fn unbound() -> Self {
|
||||
fn unbound(unbound_visibility_constraint_id: ScopedVisibilityConstraintId) -> Self {
|
||||
Self {
|
||||
live_bindings: Bindings::default(),
|
||||
constraints: Constraints::default(),
|
||||
may_be_unbound: true,
|
||||
live_bindings: Bindings::with(0),
|
||||
constraints: ConstraintsPerBinding::from_iter([Constraints::default()]),
|
||||
visibility_constraints: VisibilityConstraintPerBinding::from_iter([
|
||||
unbound_visibility_constraint_id,
|
||||
]),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add Unbound as a possibility for this symbol.
|
||||
fn set_may_be_unbound(&mut self) {
|
||||
self.may_be_unbound = true;
|
||||
}
|
||||
|
||||
/// Record a newly-encountered binding for this symbol.
|
||||
pub(super) fn record_binding(&mut self, binding_id: ScopedDefinitionId) {
|
||||
// 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.may_be_unbound = false;
|
||||
self.constraints = ConstraintsPerBinding::with_capacity(1);
|
||||
self.constraints.push(Constraints::default());
|
||||
|
||||
self.visibility_constraints = VisibilityConstraintPerBinding::with_capacity(1);
|
||||
self.visibility_constraints
|
||||
.push(ScopedVisibilityConstraintId::from_u32(0));
|
||||
}
|
||||
|
||||
/// Add given constraint to all live bindings.
|
||||
@@ -173,16 +211,41 @@ impl SymbolBindings {
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterate over currently live bindings for this symbol.
|
||||
pub(super) fn iter(&self) -> BindingIdWithConstraintsIterator {
|
||||
BindingIdWithConstraintsIterator {
|
||||
definitions: self.live_bindings.iter(),
|
||||
constraints: self.constraints.iter(),
|
||||
/// Add given visibility constraint to all live bindings.
|
||||
pub(super) fn record_visibility_constraint(
|
||||
&mut self,
|
||||
all_visibility_constraints: &mut IndexVec<
|
||||
ScopedVisibilityConstraintId,
|
||||
VisibilityConstraintRef,
|
||||
>,
|
||||
constraint: ScopedVisibilityConstraintId,
|
||||
) {
|
||||
for existing in &mut self.visibility_constraints {
|
||||
if existing == &ScopedVisibilityConstraintId::from_u32(0) {
|
||||
*existing = constraint;
|
||||
} else {
|
||||
*existing = all_visibility_constraints
|
||||
.push(VisibilityConstraintRef::And(*existing, constraint));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn may_be_unbound(&self) -> bool {
|
||||
self.may_be_unbound
|
||||
/// Iterate over currently live bindings for this symbol
|
||||
pub(super) fn iter<'map, 'db>(
|
||||
&'map self,
|
||||
all_constraints: &'map IndexVec<ScopedConstraintId, Constraint<'db>>,
|
||||
all_visibility_constraints: &'map IndexVec<
|
||||
ScopedVisibilityConstraintId,
|
||||
VisibilityConstraintRef,
|
||||
>,
|
||||
) -> BindingIdWithConstraintsIterator<'map, 'db> {
|
||||
BindingIdWithConstraintsIterator {
|
||||
all_constraints,
|
||||
all_visibility_constraints,
|
||||
definitions: self.live_bindings.iter(),
|
||||
constraints: self.constraints.iter(),
|
||||
visibility_constraints: self.visibility_constraints.iter(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,18 +257,13 @@ pub(super) struct SymbolState {
|
||||
|
||||
impl SymbolState {
|
||||
/// Return a new [`SymbolState`] representing an unbound, undeclared symbol.
|
||||
pub(super) fn undefined() -> Self {
|
||||
pub(super) fn undefined(unbound_visibility_constraint: ScopedVisibilityConstraintId) -> Self {
|
||||
Self {
|
||||
declarations: SymbolDeclarations::undeclared(),
|
||||
bindings: SymbolBindings::unbound(),
|
||||
declarations: SymbolDeclarations::undeclared(unbound_visibility_constraint),
|
||||
bindings: SymbolBindings::unbound(unbound_visibility_constraint),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add Unbound as a possibility for this symbol.
|
||||
pub(super) fn set_may_be_unbound(&mut self) {
|
||||
self.bindings.set_may_be_unbound();
|
||||
}
|
||||
|
||||
/// Record a newly-encountered binding for this symbol.
|
||||
pub(super) fn record_binding(&mut self, binding_id: ScopedDefinitionId) {
|
||||
self.bindings.record_binding(binding_id);
|
||||
@@ -216,9 +274,19 @@ impl SymbolState {
|
||||
self.bindings.record_constraint(constraint_id);
|
||||
}
|
||||
|
||||
/// Add undeclared as a possibility for this symbol.
|
||||
pub(super) fn set_may_be_undeclared(&mut self) {
|
||||
self.declarations.set_may_be_undeclared();
|
||||
/// Add given visibility constraint to all live bindings.
|
||||
pub(super) fn record_visibility_constraint(
|
||||
&mut self,
|
||||
all_visibility_constraints: &mut IndexVec<
|
||||
ScopedVisibilityConstraintId,
|
||||
VisibilityConstraintRef,
|
||||
>,
|
||||
constraint: ScopedVisibilityConstraintId,
|
||||
) {
|
||||
self.bindings
|
||||
.record_visibility_constraint(all_visibility_constraints, constraint);
|
||||
self.declarations
|
||||
.record_visibility_constraint(all_visibility_constraints, constraint);
|
||||
}
|
||||
|
||||
/// Record a newly-encountered declaration of this symbol.
|
||||
@@ -227,29 +295,34 @@ impl SymbolState {
|
||||
}
|
||||
|
||||
/// Merge another [`SymbolState`] into this one.
|
||||
pub(super) fn merge(&mut self, b: SymbolState) {
|
||||
pub(super) fn merge(
|
||||
&mut self,
|
||||
b: SymbolState,
|
||||
all_visibility_constraints: &mut IndexVec<
|
||||
ScopedVisibilityConstraintId,
|
||||
VisibilityConstraintRef,
|
||||
>,
|
||||
) {
|
||||
let mut a = Self {
|
||||
bindings: SymbolBindings {
|
||||
live_bindings: Bindings::default(),
|
||||
constraints: Constraints::default(),
|
||||
may_be_unbound: self.bindings.may_be_unbound || b.bindings.may_be_unbound,
|
||||
constraints: ConstraintsPerBinding::default(),
|
||||
visibility_constraints: VisibilityConstraintPerBinding::default(),
|
||||
},
|
||||
declarations: SymbolDeclarations {
|
||||
live_declarations: self.declarations.live_declarations.clone(),
|
||||
may_be_undeclared: self.declarations.may_be_undeclared
|
||||
|| b.declarations.may_be_undeclared,
|
||||
visibility_constraints: VisibilityConstraintPerBinding::default(),
|
||||
},
|
||||
};
|
||||
|
||||
std::mem::swap(&mut a, self);
|
||||
self.declarations
|
||||
.live_declarations
|
||||
.union(&b.declarations.live_declarations);
|
||||
|
||||
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_vis_constraints_iter = a.bindings.visibility_constraints.into_iter();
|
||||
let mut b_vis_constraints_iter = b.bindings.visibility_constraints.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 +334,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,
|
||||
visibility_constraints_iter: &mut VisibilityConstraintsIntoIterator,
|
||||
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 +347,14 @@ impl SymbolState {
|
||||
let constraints = constraints_iter
|
||||
.next()
|
||||
.expect("definitions and constraints length mismatch");
|
||||
let visibility_constraints = visibility_constraints_iter
|
||||
.next()
|
||||
.expect("definitions and visibility_constraints length mismatch");
|
||||
merged.bindings.constraints.push(constraints);
|
||||
merged
|
||||
.bindings
|
||||
.visibility_constraints
|
||||
.push(visibility_constraints);
|
||||
};
|
||||
|
||||
loop {
|
||||
@@ -279,17 +362,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_vis_constraints_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_vis_constraints_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_vis_constraints_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 +396,7 @@ impl SymbolState {
|
||||
let a_constraints = a_constraints_iter
|
||||
.next()
|
||||
.expect("definitions and constraints length mismatch");
|
||||
|
||||
// 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,23 +405,132 @@ impl SymbolState {
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
.intersect(&a_constraints);
|
||||
|
||||
// TODO: documentation
|
||||
// SAFETY: See above
|
||||
let a_vis_constraint = a_vis_constraints_iter
|
||||
.next()
|
||||
.expect("visibility_constraints length mismatch");
|
||||
let current = self.bindings.visibility_constraints.last_mut().unwrap();
|
||||
match (
|
||||
&all_visibility_constraints[*current],
|
||||
&all_visibility_constraints[a_vis_constraint],
|
||||
) {
|
||||
(_, VisibilityConstraintRef::Negated(id)) if current == id => {
|
||||
*current = ScopedVisibilityConstraintId::from_u32(0);
|
||||
}
|
||||
|
||||
(VisibilityConstraintRef::Negated(id), _)
|
||||
if *id == a_vis_constraint =>
|
||||
{
|
||||
*current = ScopedVisibilityConstraintId::from_u32(0);
|
||||
}
|
||||
_ => {
|
||||
let constraint_id = all_visibility_constraints
|
||||
.push(VisibilityConstraintRef::Or(*current, a_vis_constraint));
|
||||
*current = constraint_id;
|
||||
}
|
||||
}
|
||||
|
||||
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_vis_constraints_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_vis_constraints_iter,
|
||||
self,
|
||||
);
|
||||
opt_b_def = b_defs_iter.next();
|
||||
}
|
||||
(None, None) => break,
|
||||
}
|
||||
}
|
||||
|
||||
// Same as above, but for declarations.
|
||||
let mut a_decls_iter = a.declarations.live_declarations.iter();
|
||||
let mut b_decls_iter = b.declarations.live_declarations.iter();
|
||||
let mut a_vis_constraints_iter = a.declarations.visibility_constraints.into_iter();
|
||||
let mut b_vis_constraints_iter = b.declarations.visibility_constraints.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, conditions_iter: &mut VisibilityConstraintsIntoIterator, merged: &mut Self| {
|
||||
merged.declarations.live_declarations.insert(decl);
|
||||
let conditions = conditions_iter
|
||||
.next()
|
||||
.expect("declarations and visibility_constraints length mismatch");
|
||||
merged.declarations.visibility_constraints.push(conditions);
|
||||
};
|
||||
|
||||
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_vis_constraints_iter, self);
|
||||
opt_a_decl = a_decls_iter.next();
|
||||
}
|
||||
std::cmp::Ordering::Greater => {
|
||||
push(b_decl, &mut b_vis_constraints_iter, self);
|
||||
opt_b_decl = b_decls_iter.next();
|
||||
}
|
||||
std::cmp::Ordering::Equal => {
|
||||
push(a_decl, &mut b_vis_constraints_iter, self);
|
||||
|
||||
let a_vis_constraint = a_vis_constraints_iter
|
||||
.next()
|
||||
.expect("declarations and visibility_constraints length mismatch");
|
||||
let current = self.declarations.visibility_constraints.last_mut().unwrap();
|
||||
match (
|
||||
&all_visibility_constraints[*current],
|
||||
&all_visibility_constraints[a_vis_constraint],
|
||||
) {
|
||||
(_, VisibilityConstraintRef::Negated(id)) if current == id => {
|
||||
*current = ScopedVisibilityConstraintId::from_u32(0);
|
||||
}
|
||||
|
||||
(VisibilityConstraintRef::Negated(id), _)
|
||||
if *id == a_vis_constraint =>
|
||||
{
|
||||
*current = ScopedVisibilityConstraintId::from_u32(0);
|
||||
}
|
||||
_ => {
|
||||
let constraint_id = all_visibility_constraints
|
||||
.push(VisibilityConstraintRef::Or(*current, a_vis_constraint));
|
||||
*current = constraint_id;
|
||||
}
|
||||
}
|
||||
|
||||
opt_a_decl = a_decls_iter.next();
|
||||
opt_b_decl = b_decls_iter.next();
|
||||
}
|
||||
},
|
||||
(Some(a_decl), None) => {
|
||||
push(a_decl, &mut a_vis_constraints_iter, self);
|
||||
opt_a_decl = a_decls_iter.next();
|
||||
}
|
||||
(None, Some(b_decl)) => {
|
||||
push(b_decl, &mut b_vis_constraints_iter, self);
|
||||
opt_b_decl = b_decls_iter.next();
|
||||
}
|
||||
(None, None) => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn bindings(&self) -> &SymbolBindings {
|
||||
@@ -332,54 +540,58 @@ impl SymbolState {
|
||||
pub(super) fn declarations(&self) -> &SymbolDeclarations {
|
||||
&self.declarations
|
||||
}
|
||||
|
||||
/// Could the symbol be unbound?
|
||||
pub(super) fn may_be_unbound(&self) -> bool {
|
||||
self.bindings.may_be_unbound()
|
||||
}
|
||||
}
|
||||
|
||||
/// The default state of a symbol, if we've seen no definitions of it, is undefined (that is,
|
||||
/// both unbound and undeclared).
|
||||
impl Default for SymbolState {
|
||||
fn default() -> Self {
|
||||
SymbolState::undefined()
|
||||
}
|
||||
}
|
||||
|
||||
/// A single binding (as [`ScopedDefinitionId`]) with an iterator of its applicable
|
||||
/// [`ScopedConstraintId`].
|
||||
#[derive(Debug)]
|
||||
pub(super) struct BindingIdWithConstraints<'a> {
|
||||
pub(super) struct BindingIdWithConstraints<'map, 'db> {
|
||||
pub(super) definition: ScopedDefinitionId,
|
||||
pub(super) constraint_ids: ConstraintIdIterator<'a>,
|
||||
pub(super) constraint_ids: ConstraintIdIterator<'map>,
|
||||
pub(super) all_constraints: &'map IndexVec<ScopedConstraintId, Constraint<'db>>,
|
||||
pub(super) all_visibility_constraints:
|
||||
&'map IndexVec<ScopedVisibilityConstraintId, VisibilityConstraintRef>,
|
||||
pub(super) visibility_constraint: ScopedVisibilityConstraintId,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct BindingIdWithConstraintsIterator<'a> {
|
||||
definitions: BindingsIterator<'a>,
|
||||
constraints: ConstraintsIterator<'a>,
|
||||
pub(super) struct BindingIdWithConstraintsIterator<'map, 'db> {
|
||||
all_constraints: &'map IndexVec<ScopedConstraintId, Constraint<'db>>,
|
||||
all_visibility_constraints:
|
||||
&'map IndexVec<ScopedVisibilityConstraintId, VisibilityConstraintRef>,
|
||||
definitions: BindingsIterator<'map>,
|
||||
constraints: ConstraintsIterator<'map>,
|
||||
visibility_constraints: VisibilityConstraintsIterator<'map>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for BindingIdWithConstraintsIterator<'a> {
|
||||
type Item = BindingIdWithConstraints<'a>;
|
||||
impl<'map, 'db> Iterator for BindingIdWithConstraintsIterator<'map, 'db> {
|
||||
type Item = BindingIdWithConstraints<'map, 'db>;
|
||||
|
||||
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.visibility_constraints.next(),
|
||||
) {
|
||||
(None, None, None) => None,
|
||||
(Some(def), Some(constraints), Some(visibility_constraint_id)) => {
|
||||
Some(BindingIdWithConstraints {
|
||||
definition: ScopedDefinitionId::from_u32(def),
|
||||
constraint_ids: ConstraintIdIterator {
|
||||
wrapped: constraints.iter(),
|
||||
},
|
||||
all_constraints: self.all_constraints,
|
||||
all_visibility_constraints: self.all_visibility_constraints,
|
||||
visibility_constraint: *visibility_constraint_id,
|
||||
})
|
||||
}
|
||||
// SAFETY: see above.
|
||||
_ => unreachable!("definitions and constraints length mismatch"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::iter::FusedIterator for BindingIdWithConstraintsIterator<'_> {}
|
||||
impl std::iter::FusedIterator for BindingIdWithConstraintsIterator<'_, '_> {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct ConstraintIdIterator<'a> {
|
||||
@@ -396,193 +608,215 @@ impl Iterator for ConstraintIdIterator<'_> {
|
||||
|
||||
impl std::iter::FusedIterator for ConstraintIdIterator<'_> {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct DeclarationIdIterator<'a> {
|
||||
inner: DeclarationsIterator<'a>,
|
||||
pub(super) struct DeclarationIdIterator<'map, 'db> {
|
||||
pub(crate) all_constraints: &'map IndexVec<ScopedConstraintId, Constraint<'db>>,
|
||||
pub(crate) all_visibility_constraints:
|
||||
&'map IndexVec<ScopedVisibilityConstraintId, VisibilityConstraintRef>,
|
||||
pub(crate) inner: DeclarationsIterator<'map>,
|
||||
pub(crate) visibility_constraints: VisibilityConstraintsIterator<'map>,
|
||||
}
|
||||
|
||||
impl Iterator for DeclarationIdIterator<'_> {
|
||||
type Item = ScopedDefinitionId;
|
||||
impl<'map, 'db> Iterator for DeclarationIdIterator<'map, 'db> {
|
||||
type Item = (
|
||||
ScopedDefinitionId,
|
||||
&'map IndexVec<ScopedConstraintId, Constraint<'db>>,
|
||||
&'map IndexVec<ScopedVisibilityConstraintId, VisibilityConstraintRef>,
|
||||
ScopedVisibilityConstraintId,
|
||||
);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.inner.next().map(ScopedDefinitionId::from_u32)
|
||||
match (self.inner.next(), self.visibility_constraints.next()) {
|
||||
(None, None) => None,
|
||||
(Some(declaration), Some(visibility_constraints_id)) => Some((
|
||||
ScopedDefinitionId::from_u32(declaration),
|
||||
self.all_constraints,
|
||||
self.all_visibility_constraints,
|
||||
*visibility_constraints_id,
|
||||
)),
|
||||
// SAFETY: see above.
|
||||
_ => unreachable!("declarations and visibility_constraints length mismatch"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::iter::FusedIterator for DeclarationIdIterator<'_> {}
|
||||
impl std::iter::FusedIterator for DeclarationIdIterator<'_, '_> {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{ScopedConstraintId, ScopedDefinitionId, SymbolState};
|
||||
// use super::*;
|
||||
|
||||
fn assert_bindings(symbol: &SymbolState, may_be_unbound: bool, expected: &[&str]) {
|
||||
assert_eq!(symbol.may_be_unbound(), may_be_unbound);
|
||||
let actual = symbol
|
||||
.bindings()
|
||||
.iter()
|
||||
.map(|def_id_with_constraints| {
|
||||
format!(
|
||||
"{}<{}>",
|
||||
def_id_with_constraints.definition.as_u32(),
|
||||
def_id_with_constraints
|
||||
.constraint_ids
|
||||
.map(ScopedConstraintId::as_u32)
|
||||
.map(|idx| idx.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
// #[track_caller]
|
||||
// fn assert_bindings(symbol: &SymbolState, may_be_unbound: bool, expected: &[&str]) {
|
||||
// assert_eq!(symbol.bindings.may_be_unbound, may_be_unbound);
|
||||
// let mut actual = symbol
|
||||
// .bindings()
|
||||
// .iter()
|
||||
// .map(|def_id_with_constraints| {
|
||||
// format!(
|
||||
// "{}<{}>",
|
||||
// def_id_with_constraints.definition.as_u32(),
|
||||
// def_id_with_constraints
|
||||
// .constraint_ids
|
||||
// .map(ScopedConstraintId::as_u32)
|
||||
// .map(|idx| idx.to_string())
|
||||
// .collect::<Vec<_>>()
|
||||
// .join(", ")
|
||||
// )
|
||||
// })
|
||||
// .collect::<Vec<_>>();
|
||||
// actual.reverse();
|
||||
// assert_eq!(actual, expected);
|
||||
// }
|
||||
|
||||
pub(crate) fn assert_declarations(
|
||||
symbol: &SymbolState,
|
||||
may_be_undeclared: bool,
|
||||
expected: &[u32],
|
||||
) {
|
||||
assert_eq!(symbol.declarations.may_be_undeclared(), may_be_undeclared);
|
||||
let actual = symbol
|
||||
.declarations()
|
||||
.iter()
|
||||
.map(ScopedDefinitionId::as_u32)
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
// #[track_caller]
|
||||
// pub(crate) fn assert_declarations(
|
||||
// symbol: &SymbolState,
|
||||
// may_be_undeclared: bool,
|
||||
// expected: &[u32],
|
||||
// ) {
|
||||
// assert_eq!(symbol.declarations.may_be_undeclared(), may_be_undeclared);
|
||||
// let mut actual = symbol
|
||||
// .declarations()
|
||||
// .iter()
|
||||
// .map(|(d, _)| d.as_u32())
|
||||
// .collect::<Vec<_>>();
|
||||
// actual.reverse();
|
||||
// assert_eq!(actual, expected);
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn unbound() {
|
||||
let sym = SymbolState::undefined();
|
||||
// #[test]
|
||||
// fn unbound() {
|
||||
// let sym = SymbolState::undefined();
|
||||
|
||||
assert_bindings(&sym, true, &[]);
|
||||
}
|
||||
// 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() {
|
||||
let sym = SymbolState::undefined();
|
||||
// #[test]
|
||||
// fn no_declaration() {
|
||||
// let sym = SymbolState::undefined();
|
||||
|
||||
assert_declarations(&sym, true, &[]);
|
||||
}
|
||||
// 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]);
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
use crate::semantic_index::use_def::{ScopedConstraintId, ScopedVisibilityConstraintId};
|
||||
|
||||
/// TODO
|
||||
///
|
||||
/// Used to represent active branching conditions that apply to a particular definition.
|
||||
/// A definition can either be conditional on a specific constraint from a `if`, `elif`,
|
||||
/// `while` statement, an `if`-expression, or a Boolean expression. Or it can be marked
|
||||
/// as 'ambiguous' if it occurred in a control-flow path that is not conditional on any
|
||||
/// specific expression that can be statically analyzed (`for` loop, `try` ... `except`).
|
||||
///
|
||||
///
|
||||
/// For example:
|
||||
/// ```py
|
||||
/// a = 1 # no visibility constraints
|
||||
///
|
||||
/// if test1:
|
||||
/// b = 1 # Constraint(test1)
|
||||
///
|
||||
/// if test2:
|
||||
/// c = 1 # Constraint(test1), Constraint(test2)
|
||||
///
|
||||
/// for _ in range(10):
|
||||
/// d = 1 # Constraint(test1), Ambiguous
|
||||
/// else:
|
||||
/// d = 1 # Constraint(~test1)
|
||||
/// ```
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub(crate) enum VisibilityConstraintRef {
|
||||
None,
|
||||
Single(ScopedConstraintId),
|
||||
Negated(ScopedVisibilityConstraintId),
|
||||
And(ScopedVisibilityConstraintId, ScopedVisibilityConstraintId),
|
||||
Or(ScopedVisibilityConstraintId, ScopedVisibilityConstraintId),
|
||||
}
|
||||
@@ -33,6 +33,7 @@ use crate::types::diagnostic::INVALID_TYPE_FORM;
|
||||
use crate::types::mro::{Mro, MroError, MroIterator};
|
||||
use crate::types::narrow::narrowing_constraint;
|
||||
use crate::{Db, FxOrderSet, Module, Program, PythonVersion};
|
||||
pub(crate) use static_truthiness::static_visibility;
|
||||
|
||||
mod builder;
|
||||
mod call;
|
||||
@@ -44,6 +45,7 @@ mod infer;
|
||||
mod mro;
|
||||
mod narrow;
|
||||
mod signatures;
|
||||
mod static_truthiness;
|
||||
mod string_annotation;
|
||||
mod unpacker;
|
||||
|
||||
@@ -69,61 +71,64 @@ pub fn check_types(db: &dyn Db, file: File) -> TypeCheckDiagnostics {
|
||||
|
||||
/// Infer the public type of a symbol (its type as seen from outside its scope).
|
||||
fn symbol_by_id<'db>(db: &'db dyn Db, scope: ScopeId<'db>, symbol: ScopedSymbolId) -> Symbol<'db> {
|
||||
let _span = tracing::trace_span!("symbol_by_id", ?symbol).entered();
|
||||
|
||||
let use_def = use_def_map(db, scope);
|
||||
|
||||
// If the symbol is declared, the public type is based on declarations; otherwise, it's based
|
||||
// on inference from bindings.
|
||||
if use_def.has_public_declarations(symbol) {
|
||||
let declarations = use_def.public_declarations(symbol);
|
||||
// If the symbol is undeclared in some paths, include the inferred type in the public type.
|
||||
let undeclared_ty = if declarations.may_be_undeclared() {
|
||||
Some(
|
||||
bindings_ty(db, use_def.public_bindings(symbol))
|
||||
.map(|bindings_ty| Symbol::Type(bindings_ty, use_def.public_boundness(symbol)))
|
||||
.unwrap_or(Symbol::Unbound),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// Intentionally ignore conflicting declared types; that's not our problem, it's the
|
||||
// problem of the module we are importing from.
|
||||
// let declaredness = use_def.public_declarations(symbol).declaredness(db);
|
||||
|
||||
// TODO: Our handling of boundness currently only depends on bindings, and ignores
|
||||
// declarations. This is inconsistent, since we only look at bindings if the symbol
|
||||
// may be undeclared. Consider the following example:
|
||||
// ```py
|
||||
// x: int
|
||||
//
|
||||
// if flag:
|
||||
// y: int
|
||||
// else
|
||||
// y = 3
|
||||
// ```
|
||||
// If we import from this module, we will currently report `x` as a definitely-bound
|
||||
// symbol (even though it has no bindings at all!) but report `y` as possibly-unbound
|
||||
// (even though every path has either a binding or a declaration for it.)
|
||||
let declarations = use_def.public_declarations(symbol);
|
||||
let declared_ty = declarations_ty(db, declarations);
|
||||
|
||||
match undeclared_ty {
|
||||
Some(Symbol::Type(ty, boundness)) => Symbol::Type(
|
||||
declarations_ty(db, declarations, Some(ty)).unwrap_or_else(|(ty, _)| ty),
|
||||
boundness,
|
||||
),
|
||||
None | Some(Symbol::Unbound) => Symbol::Type(
|
||||
declarations_ty(db, declarations, None).unwrap_or_else(|(ty, _)| ty),
|
||||
Boundness::Bound,
|
||||
),
|
||||
let bindings = use_def.public_bindings(symbol); // TODO: short circuit if declaredness is Bound
|
||||
let inferred_ty = bindings_ty(db, bindings);
|
||||
|
||||
match declared_ty {
|
||||
// Symbol is declared, trust the declared type
|
||||
Ok(symbol @ Symbol::Type(_, Boundness::Bound)) => symbol,
|
||||
// Symbol is possibly declared
|
||||
Ok(Symbol::Type(declared_ty, Boundness::PossiblyUnbound)) => {
|
||||
match inferred_ty {
|
||||
// Symbol is possibly undeclared and definitely unbound
|
||||
Symbol::Unbound => Symbol::Type(declared_ty, Boundness::Bound),
|
||||
// Symbol is possibly undeclared and possibly bound
|
||||
inferred @ Symbol::Type(_, Boundness::Bound) => inferred,
|
||||
// Symbol is possibly undeclared and possibly unbound
|
||||
Symbol::Type(inferred_ty, Boundness::PossiblyUnbound) => Symbol::Type(
|
||||
UnionType::from_elements(db, [declared_ty, inferred_ty].iter().copied()),
|
||||
Boundness::PossiblyUnbound,
|
||||
),
|
||||
}
|
||||
}
|
||||
// Symbol is undeclared
|
||||
Ok(Symbol::Unbound) => inferred_ty,
|
||||
// Symbol is possibly undeclared
|
||||
Err((declared_ty, _)) => {
|
||||
// TODO: conflicting declarations error
|
||||
declared_ty.into()
|
||||
}
|
||||
} else {
|
||||
bindings_ty(db, use_def.public_bindings(symbol))
|
||||
.map(|bindings_ty| Symbol::Type(bindings_ty, use_def.public_boundness(symbol)))
|
||||
.unwrap_or(Symbol::Unbound)
|
||||
}
|
||||
|
||||
// TODO (ticket: https://github.com/astral-sh/ruff/issues/14297) Our handling of boundness
|
||||
// currently only depends on bindings, and ignores declarations. This is inconsistent, since
|
||||
// we only look at bindings if the symbol may be undeclared. Consider the following example:
|
||||
// ```py
|
||||
// x: int
|
||||
//
|
||||
// if flag:
|
||||
// y: int
|
||||
// else
|
||||
// y = 3
|
||||
// ```
|
||||
// If we import from this module, we will currently report `x` as a definitely-bound symbol
|
||||
// (even though it has no bindings at all!) but report `y` as possibly-unbound (even though
|
||||
// every path has either a binding or a declaration for it.)
|
||||
}
|
||||
|
||||
/// Shorthand for `symbol_by_id` that takes a symbol name instead of an ID.
|
||||
fn symbol<'db>(db: &'db dyn Db, scope: ScopeId<'db>, name: &str) -> Symbol<'db> {
|
||||
let _span = tracing::trace_span!("symbol", ?name).entered();
|
||||
|
||||
// We don't need to check for `typing_extensions` here, because `typing_extensions.TYPE_CHECKING`
|
||||
// is just a re-export of `typing.TYPE_CHECKING`.
|
||||
if name == "TYPE_CHECKING"
|
||||
@@ -247,46 +252,81 @@ fn definition_expression_ty<'db>(
|
||||
fn bindings_ty<'db>(
|
||||
db: &'db dyn Db,
|
||||
bindings_with_constraints: BindingWithConstraintsIterator<'_, 'db>,
|
||||
) -> Option<Type<'db>> {
|
||||
let mut def_types = bindings_with_constraints.map(
|
||||
|BindingWithConstraints {
|
||||
binding,
|
||||
constraints,
|
||||
}| {
|
||||
let mut constraint_tys = constraints
|
||||
.filter_map(|constraint| narrowing_constraint(db, constraint, binding))
|
||||
.peekable();
|
||||
) -> Symbol<'db> {
|
||||
let mut is_unbound_visible = false;
|
||||
let mut types = vec![];
|
||||
|
||||
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()
|
||||
} else {
|
||||
binding_ty
|
||||
for BindingWithConstraints {
|
||||
binding,
|
||||
constraints,
|
||||
all_constraints,
|
||||
all_visibility_constraints,
|
||||
visibility_constraint,
|
||||
} in bindings_with_constraints
|
||||
{
|
||||
let static_visibility = static_visibility(
|
||||
db,
|
||||
all_constraints,
|
||||
all_visibility_constraints,
|
||||
visibility_constraint,
|
||||
);
|
||||
|
||||
let Some(binding) = binding else {
|
||||
if !static_visibility.is_always_false() {
|
||||
// TODO: also distinguish between always-true and ambiguous?
|
||||
is_unbound_visible = true;
|
||||
}
|
||||
},
|
||||
);
|
||||
continue;
|
||||
};
|
||||
|
||||
if let Some(first) = def_types.next() {
|
||||
if let Some(second) = def_types.next() {
|
||||
Some(UnionType::from_elements(
|
||||
db,
|
||||
[first, second].into_iter().chain(def_types),
|
||||
))
|
||||
if static_visibility.is_always_false() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut constraint_tys = constraints
|
||||
.filter_map(|constraint| narrowing_constraint(db, constraint, binding))
|
||||
.peekable();
|
||||
|
||||
let binding_ty = binding_ty(db, binding);
|
||||
let ty = 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
|
||||
} else {
|
||||
Some(first)
|
||||
binding_ty
|
||||
};
|
||||
|
||||
types.push(ty);
|
||||
}
|
||||
|
||||
let mut types = types.into_iter();
|
||||
|
||||
if let Some(first) = types.next() {
|
||||
let boundness = if is_unbound_visible {
|
||||
Boundness::PossiblyUnbound
|
||||
} else {
|
||||
Boundness::Bound
|
||||
};
|
||||
|
||||
if let Some(second) = types.next() {
|
||||
Symbol::Type(
|
||||
UnionType::from_elements(db, [first, second].into_iter().chain(types)),
|
||||
boundness,
|
||||
)
|
||||
} else {
|
||||
Symbol::Type(first, boundness)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
Symbol::Unbound
|
||||
}
|
||||
}
|
||||
|
||||
/// The result of looking up a declared type from declarations; see [`declarations_ty`].
|
||||
type DeclaredTypeResult<'db> = Result<Type<'db>, (Type<'db>, Box<[Type<'db>]>)>;
|
||||
type DeclaredTypeResult<'db> = Result<Symbol<'db>, (Type<'db>, Box<[Type<'db>]>)>;
|
||||
|
||||
/// Build a declared type from a [`DeclarationsIterator`].
|
||||
///
|
||||
@@ -304,40 +344,67 @@ type DeclaredTypeResult<'db> = Result<Type<'db>, (Type<'db>, Box<[Type<'db>]>)>;
|
||||
fn declarations_ty<'db>(
|
||||
db: &'db dyn Db,
|
||||
declarations: DeclarationsIterator<'_, 'db>,
|
||||
undeclared_ty: Option<Type<'db>>,
|
||||
) -> DeclaredTypeResult<'db> {
|
||||
let mut declaration_types = declarations.map(|declaration| declaration_ty(db, declaration));
|
||||
let mut is_unbound_visible = false;
|
||||
let mut types = vec![];
|
||||
|
||||
let Some(first) = declaration_types.next() else {
|
||||
if let Some(undeclared_ty) = undeclared_ty {
|
||||
// Short-circuit to return the undeclared type if there are no declarations.
|
||||
return Ok(undeclared_ty);
|
||||
for (declaration, all_constraints, all_visibility_constraints, visibility_constraint) in
|
||||
declarations
|
||||
{
|
||||
let static_visibility = static_visibility(
|
||||
db,
|
||||
all_constraints,
|
||||
all_visibility_constraints,
|
||||
visibility_constraint,
|
||||
);
|
||||
|
||||
let Some(declaration) = declaration else {
|
||||
if !static_visibility.is_always_false() {
|
||||
is_unbound_visible = true;
|
||||
}
|
||||
continue;
|
||||
};
|
||||
|
||||
if static_visibility.is_always_false() {
|
||||
continue;
|
||||
}
|
||||
panic!("declarations_ty must not be called with zero declarations and no undeclared_ty");
|
||||
};
|
||||
|
||||
let mut conflicting: Vec<Type<'db>> = vec![];
|
||||
let mut builder = UnionBuilder::new(db).add(first);
|
||||
for other in declaration_types {
|
||||
if !first.is_equivalent_to(db, other) {
|
||||
conflicting.push(other);
|
||||
let ty = declaration_ty(db, declaration);
|
||||
types.push(ty);
|
||||
}
|
||||
|
||||
let mut types = types.into_iter();
|
||||
|
||||
if let Some(first) = types.next() {
|
||||
let mut conflicting: Vec<Type<'db>> = vec![];
|
||||
let declared_ty = if let Some(second) = types.next() {
|
||||
let mut builder = UnionBuilder::new(db).add(first);
|
||||
for other in [second].into_iter().chain(types) {
|
||||
if !first.is_equivalent_to(db, other) {
|
||||
conflicting.push(other);
|
||||
}
|
||||
builder = builder.add(other);
|
||||
}
|
||||
builder.build()
|
||||
} else {
|
||||
first
|
||||
};
|
||||
if conflicting.is_empty() {
|
||||
let boundness = if is_unbound_visible {
|
||||
Boundness::PossiblyUnbound
|
||||
} else {
|
||||
Boundness::Bound
|
||||
};
|
||||
|
||||
Ok(Symbol::Type(declared_ty, boundness))
|
||||
} else {
|
||||
Err((
|
||||
declared_ty,
|
||||
[first].into_iter().chain(conflicting).collect(),
|
||||
))
|
||||
}
|
||||
builder = builder.add(other);
|
||||
}
|
||||
// Avoid considering the undeclared type for the conflicting declaration diagnostics. It
|
||||
// should still be part of the declared type.
|
||||
if let Some(undeclared_ty) = undeclared_ty {
|
||||
builder = builder.add(undeclared_ty);
|
||||
}
|
||||
let declared_ty = builder.build();
|
||||
|
||||
if conflicting.is_empty() {
|
||||
Ok(declared_ty)
|
||||
} else {
|
||||
Err((
|
||||
declared_ty,
|
||||
[first].into_iter().chain(conflicting).collect(),
|
||||
))
|
||||
Ok(Symbol::Unbound)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1580,7 +1647,7 @@ impl<'db> Type<'db> {
|
||||
///
|
||||
/// This is used to determine the value that would be returned
|
||||
/// when `bool(x)` is called on an object `x`.
|
||||
fn bool(&self, db: &'db dyn Db) -> Truthiness {
|
||||
pub(crate) fn bool(&self, db: &'db dyn Db) -> Truthiness {
|
||||
match self {
|
||||
Type::Any | Type::Todo(_) | Type::Never | Type::Unknown => Truthiness::Ambiguous,
|
||||
Type::FunctionLiteral(_) => Truthiness::AlwaysTrue,
|
||||
@@ -2007,7 +2074,7 @@ impl<'db> Type<'db> {
|
||||
/// but it's a useful fallback for us in order to infer `Literal` types from `sys.version_info` comparisons.
|
||||
fn version_info_tuple(db: &'db dyn Db) -> Self {
|
||||
let python_version = Program::get(db).python_version(db);
|
||||
let int_instance_ty = KnownClass::Int.to_instance(db);
|
||||
let int_instance_ty = Type::Unknown;
|
||||
|
||||
// TODO: just grab this type from typeshed (it's a `sys._ReleaseLevel` type alias there)
|
||||
let release_level_ty = {
|
||||
@@ -2859,11 +2926,15 @@ pub enum Truthiness {
|
||||
}
|
||||
|
||||
impl Truthiness {
|
||||
const fn is_ambiguous(self) -> bool {
|
||||
pub(crate) const fn is_ambiguous(self) -> bool {
|
||||
matches!(self, Truthiness::Ambiguous)
|
||||
}
|
||||
|
||||
const fn negate(self) -> Self {
|
||||
pub(crate) const fn is_always_false(self) -> bool {
|
||||
matches!(self, Truthiness::AlwaysFalse)
|
||||
}
|
||||
|
||||
pub(crate) const fn negate(self) -> Self {
|
||||
match self {
|
||||
Self::AlwaysTrue => Self::AlwaysFalse,
|
||||
Self::AlwaysFalse => Self::AlwaysTrue,
|
||||
@@ -2871,6 +2942,14 @@ impl Truthiness {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) const fn negate_if(self, condition: bool) -> Self {
|
||||
if condition {
|
||||
self.negate()
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
fn into_type(self, db: &dyn Db) -> Type {
|
||||
match self {
|
||||
Self::AlwaysTrue => Type::BooleanLiteral(true),
|
||||
|
||||
@@ -164,12 +164,28 @@ pub(crate) fn infer_deferred_types<'db>(
|
||||
TypeInferenceBuilder::new(db, InferenceRegion::Deferred(definition), index).finish()
|
||||
}
|
||||
|
||||
/// TODO: get rid of this? if not, at least build a cycle-recovery-variant of [`infer_expression_types`],
|
||||
/// which is only used in static visibility analysis.
|
||||
pub(crate) fn infer_expression_types_cycle_recovery<'db>(
|
||||
db: &'db dyn Db,
|
||||
_cycle: &salsa::Cycle,
|
||||
expr: Expression<'db>,
|
||||
) -> TypeInference<'db> {
|
||||
tracing::trace!("infer_expression_types_cycle_recovery");
|
||||
let mut inference = TypeInference::empty(expr.scope(db));
|
||||
inference.expressions.insert(
|
||||
expr.node_ref(db).scoped_expression_id(db, expr.scope(db)),
|
||||
Type::Unknown,
|
||||
);
|
||||
inference
|
||||
}
|
||||
|
||||
/// Infer all types for an [`Expression`] (including sub-expressions).
|
||||
/// Use rarely; only for cases where we'd otherwise risk double-inferring an expression: RHS of an
|
||||
/// assignment, which might be unpacking/multi-target and thus part of multiple definitions, or a
|
||||
/// type narrowing guard expression (e.g. if statement test node).
|
||||
#[allow(unused)]
|
||||
#[salsa::tracked(return_ref)]
|
||||
#[salsa::tracked(return_ref, recovery_fn=infer_expression_types_cycle_recovery)]
|
||||
pub(crate) fn infer_expression_types<'db>(
|
||||
db: &'db dyn Db,
|
||||
expression: Expression<'db>,
|
||||
@@ -824,14 +840,15 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
debug_assert!(binding.is_binding(self.db()));
|
||||
let use_def = self.index.use_def_map(binding.file_scope(self.db()));
|
||||
let declarations = use_def.declarations_at_binding(binding);
|
||||
let undeclared_ty = if declarations.may_be_undeclared() {
|
||||
Some(Type::Unknown)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// let undeclared_ty = if declarations.clone().may_be_undeclared(self.db) {
|
||||
// Symbol::Type(Type::Unknown, Boundness::Bound)
|
||||
// } else {
|
||||
// Symbol::Unbound
|
||||
// };
|
||||
let mut bound_ty = ty;
|
||||
let declared_ty = declarations_ty(self.db(), declarations, undeclared_ty).unwrap_or_else(
|
||||
|(ty, conflicting)| {
|
||||
let declared_ty = declarations_ty(self.db(), declarations)
|
||||
.map(|s| s.ignore_possibly_unbound().unwrap_or(Type::Unknown))
|
||||
.unwrap_or_else(|(ty, conflicting)| {
|
||||
// TODO point out the conflicting declarations in the diagnostic?
|
||||
let symbol_table = self.index.symbol_table(binding.file_scope(self.db()));
|
||||
let symbol_name = symbol_table.symbol(binding.symbol(self.db())).name();
|
||||
@@ -844,8 +861,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
),
|
||||
);
|
||||
ty
|
||||
},
|
||||
);
|
||||
});
|
||||
if !bound_ty.is_assignable_to(self.db(), declared_ty) {
|
||||
report_invalid_assignment(&self.context, node, declared_ty, bound_ty);
|
||||
// allow declarations to override inference in case of invalid assignment
|
||||
@@ -860,7 +876,9 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
let use_def = self.index.use_def_map(declaration.file_scope(self.db()));
|
||||
let prior_bindings = use_def.bindings_at_declaration(declaration);
|
||||
// unbound_ty is Never because for this check we don't care about unbound
|
||||
let inferred_ty = bindings_ty(self.db(), prior_bindings).unwrap_or(Type::Never);
|
||||
let inferred_ty = bindings_ty(self.db(), prior_bindings)
|
||||
.ignore_possibly_unbound()
|
||||
.unwrap_or(Type::Never);
|
||||
let ty = if inferred_ty.is_assignable_to(self.db(), ty) {
|
||||
ty
|
||||
} else {
|
||||
@@ -1764,13 +1782,24 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
fn infer_match_pattern(&mut self, pattern: &ast::Pattern) {
|
||||
// TODO(dhruvmanila): Add a Salsa query for inferring pattern types and matching against
|
||||
// the subject expression: https://github.com/astral-sh/ruff/pull/13147#discussion_r1739424510
|
||||
match pattern {
|
||||
ast::Pattern::MatchValue(match_value) => {
|
||||
self.infer_standalone_expression(&match_value.value);
|
||||
}
|
||||
_ => {
|
||||
self.infer_match_pattern_impl(pattern);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn infer_match_pattern_impl(&mut self, pattern: &ast::Pattern) {
|
||||
match pattern {
|
||||
ast::Pattern::MatchValue(match_value) => {
|
||||
self.infer_expression(&match_value.value);
|
||||
}
|
||||
ast::Pattern::MatchSequence(match_sequence) => {
|
||||
for pattern in &match_sequence.patterns {
|
||||
self.infer_match_pattern(pattern);
|
||||
self.infer_match_pattern_impl(pattern);
|
||||
}
|
||||
}
|
||||
ast::Pattern::MatchMapping(match_mapping) => {
|
||||
@@ -1784,7 +1813,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
self.infer_expression(key);
|
||||
}
|
||||
for pattern in patterns {
|
||||
self.infer_match_pattern(pattern);
|
||||
self.infer_match_pattern_impl(pattern);
|
||||
}
|
||||
}
|
||||
ast::Pattern::MatchClass(match_class) => {
|
||||
@@ -1794,21 +1823,21 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
arguments,
|
||||
} = match_class;
|
||||
for pattern in &arguments.patterns {
|
||||
self.infer_match_pattern(pattern);
|
||||
self.infer_match_pattern_impl(pattern);
|
||||
}
|
||||
for keyword in &arguments.keywords {
|
||||
self.infer_match_pattern(&keyword.pattern);
|
||||
self.infer_match_pattern_impl(&keyword.pattern);
|
||||
}
|
||||
self.infer_expression(cls);
|
||||
}
|
||||
ast::Pattern::MatchAs(match_as) => {
|
||||
if let Some(pattern) = &match_as.pattern {
|
||||
self.infer_match_pattern(pattern);
|
||||
self.infer_match_pattern_impl(pattern);
|
||||
}
|
||||
}
|
||||
ast::Pattern::MatchOr(match_or) => {
|
||||
for pattern in &match_or.patterns {
|
||||
self.infer_match_pattern(pattern);
|
||||
self.infer_match_pattern_impl(pattern);
|
||||
}
|
||||
}
|
||||
ast::Pattern::MatchStar(_) | ast::Pattern::MatchSingleton(_) => {}
|
||||
@@ -3094,28 +3123,25 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
let use_def = self.index.use_def_map(file_scope_id);
|
||||
|
||||
// If we're inferring types of deferred expressions, always treat them as public symbols
|
||||
let (bindings_ty, boundness) = if self.is_deferred() {
|
||||
let bindings_ty = if self.is_deferred() {
|
||||
if let Some(symbol) = self.index.symbol_table(file_scope_id).symbol_id_by_name(id) {
|
||||
(
|
||||
bindings_ty(self.db(), use_def.public_bindings(symbol)),
|
||||
use_def.public_boundness(symbol),
|
||||
)
|
||||
bindings_ty(self.db(), use_def.public_bindings(symbol))
|
||||
} else {
|
||||
assert!(
|
||||
self.deferred_state.in_string_annotation(),
|
||||
"Expected the symbol table to create a symbol for every Name node"
|
||||
);
|
||||
(None, Boundness::PossiblyUnbound)
|
||||
// (None, Some(Boundness::PossiblyUnbound))
|
||||
Symbol::Type(Type::Unknown, Boundness::PossiblyUnbound)
|
||||
}
|
||||
} else {
|
||||
let use_id = name.scoped_use_id(self.db(), self.scope());
|
||||
(
|
||||
bindings_ty(self.db(), use_def.bindings_at_use(use_id)),
|
||||
use_def.use_boundness(use_id),
|
||||
)
|
||||
bindings_ty(self.db(), use_def.bindings_at_use(use_id))
|
||||
};
|
||||
|
||||
if boundness == Boundness::PossiblyUnbound {
|
||||
if let Symbol::Type(ty, Boundness::Bound) = bindings_ty {
|
||||
ty
|
||||
} else {
|
||||
match self.lookup_name(name) {
|
||||
Symbol::Type(looked_up_ty, looked_up_boundness) => {
|
||||
if looked_up_boundness == Boundness::PossiblyUnbound {
|
||||
@@ -3123,20 +3149,22 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
}
|
||||
|
||||
bindings_ty
|
||||
.ignore_possibly_unbound()
|
||||
.map(|ty| UnionType::from_elements(self.db(), [ty, looked_up_ty]))
|
||||
.unwrap_or(looked_up_ty)
|
||||
}
|
||||
Symbol::Unbound => {
|
||||
if bindings_ty.is_some() {
|
||||
Symbol::Unbound => match bindings_ty {
|
||||
Symbol::Type(ty, Boundness::PossiblyUnbound) => {
|
||||
report_possibly_unresolved_reference(&self.context, name);
|
||||
} else {
|
||||
report_unresolved_reference(&self.context, name);
|
||||
ty
|
||||
}
|
||||
bindings_ty.unwrap_or(Type::Unknown)
|
||||
}
|
||||
Symbol::Unbound => {
|
||||
report_unresolved_reference(&self.context, name);
|
||||
Type::Unknown
|
||||
}
|
||||
Symbol::Type(_, Boundness::Bound) => unreachable!("Handled above"),
|
||||
},
|
||||
}
|
||||
} else {
|
||||
bindings_ty.unwrap_or(Type::Unknown)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6358,9 +6386,10 @@ mod tests {
|
||||
let scope = global_scope(db, file);
|
||||
use_def_map(db, scope)
|
||||
.public_bindings(symbol_table(db, scope).symbol_id_by_name(name).unwrap())
|
||||
.next()
|
||||
.map(|b| b.binding)
|
||||
.find(Option::is_some)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.binding
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use crate::semantic_index::ast_ids::HasScopedExpressionId;
|
||||
use crate::semantic_index::constraint::{Constraint, ConstraintNode, PatternConstraint};
|
||||
use crate::semantic_index::constraint::{
|
||||
Constraint, ConstraintNode, PatternConstraint, PatternConstraintKind,
|
||||
};
|
||||
use crate::semantic_index::definition::Definition;
|
||||
use crate::semantic_index::expression::Expression;
|
||||
use crate::semantic_index::symbol::{ScopeId, ScopedSymbolId, SymbolTable};
|
||||
@@ -216,31 +218,12 @@ impl<'db> NarrowingConstraintsBuilder<'db> {
|
||||
) -> Option<NarrowingConstraints<'db>> {
|
||||
let subject = pattern.subject(self.db);
|
||||
|
||||
match pattern.pattern(self.db).node() {
|
||||
ast::Pattern::MatchValue(_) => {
|
||||
None // TODO
|
||||
}
|
||||
ast::Pattern::MatchSingleton(singleton_pattern) => {
|
||||
self.evaluate_match_pattern_singleton(subject, singleton_pattern)
|
||||
}
|
||||
ast::Pattern::MatchSequence(_) => {
|
||||
None // TODO
|
||||
}
|
||||
ast::Pattern::MatchMapping(_) => {
|
||||
None // TODO
|
||||
}
|
||||
ast::Pattern::MatchClass(_) => {
|
||||
None // TODO
|
||||
}
|
||||
ast::Pattern::MatchStar(_) => {
|
||||
None // TODO
|
||||
}
|
||||
ast::Pattern::MatchAs(_) => {
|
||||
None // TODO
|
||||
}
|
||||
ast::Pattern::MatchOr(_) => {
|
||||
None // TODO
|
||||
match pattern.kind(self.db) {
|
||||
PatternConstraintKind::Singleton(singleton) => {
|
||||
self.evaluate_match_pattern_singleton(*subject, *singleton)
|
||||
}
|
||||
// TODO: support more pattern kinds
|
||||
PatternConstraintKind::Value(_) | PatternConstraintKind::Unsupported => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -483,14 +466,14 @@ impl<'db> NarrowingConstraintsBuilder<'db> {
|
||||
|
||||
fn evaluate_match_pattern_singleton(
|
||||
&mut self,
|
||||
subject: &ast::Expr,
|
||||
pattern: &ast::PatternMatchSingleton,
|
||||
subject: Expression<'db>,
|
||||
singleton: ast::Singleton,
|
||||
) -> Option<NarrowingConstraints<'db>> {
|
||||
if let Some(ast::ExprName { id, .. }) = subject.as_name_expr() {
|
||||
if let Some(ast::ExprName { id, .. }) = subject.node_ref(self.db).as_name_expr() {
|
||||
// SAFETY: we should always have a symbol for every Name node.
|
||||
let symbol = self.symbols().symbol_id_by_name(id).unwrap();
|
||||
|
||||
let ty = match pattern.value {
|
||||
let ty = match singleton {
|
||||
ast::Singleton::None => Type::none(self.db),
|
||||
ast::Singleton::True => Type::BooleanLiteral(true),
|
||||
ast::Singleton::False => Type::BooleanLiteral(false),
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
use ruff_index::IndexVec;
|
||||
|
||||
use crate::semantic_index::{
|
||||
ast_ids::HasScopedExpressionId,
|
||||
constraint::{Constraint, ConstraintNode, PatternConstraintKind},
|
||||
visibility_constraint::VisibilityConstraintRef,
|
||||
ScopedConstraintId, ScopedVisibilityConstraintId,
|
||||
};
|
||||
use crate::types::{infer_expression_types, Truthiness};
|
||||
use crate::Db;
|
||||
|
||||
/// Analyze the statically known visibility for a given visibility constraint.
|
||||
pub(crate) fn static_visibility<'db>(
|
||||
db: &'db dyn Db,
|
||||
all_constraints: &IndexVec<ScopedConstraintId, Constraint<'db>>,
|
||||
all_visibility_constraints: &IndexVec<ScopedVisibilityConstraintId, VisibilityConstraintRef>,
|
||||
visibility_constraint_id: ScopedVisibilityConstraintId,
|
||||
) -> Truthiness {
|
||||
let visibility_constraint = &all_visibility_constraints[visibility_constraint_id];
|
||||
match visibility_constraint {
|
||||
VisibilityConstraintRef::Single(id) => {
|
||||
let constraint = &all_constraints[*id];
|
||||
|
||||
match constraint.node {
|
||||
ConstraintNode::Expression(test_expr) => {
|
||||
let inference = infer_expression_types(db, test_expr);
|
||||
let scope = test_expr.scope(db);
|
||||
let ty = inference
|
||||
.expression_ty(test_expr.node_ref(db).scoped_expression_id(db, scope));
|
||||
|
||||
ty.bool(db).negate_if(!constraint.is_positive)
|
||||
}
|
||||
ConstraintNode::Pattern(inner) => match inner.kind(db) {
|
||||
PatternConstraintKind::Value(value) => {
|
||||
let subject_expression = inner.subject(db);
|
||||
let inference = infer_expression_types(db, *subject_expression);
|
||||
let scope = subject_expression.scope(db);
|
||||
let subject_ty = inference.expression_ty(
|
||||
subject_expression
|
||||
.node_ref(db)
|
||||
.scoped_expression_id(db, scope),
|
||||
);
|
||||
|
||||
let inference = infer_expression_types(db, *value);
|
||||
let scope = value.scope(db);
|
||||
let value_ty = inference
|
||||
.expression_ty(value.node_ref(db).scoped_expression_id(db, scope));
|
||||
|
||||
if subject_ty.is_single_valued(db) {
|
||||
Truthiness::from(subject_ty.is_equivalent_to(db, value_ty))
|
||||
} else {
|
||||
Truthiness::Ambiguous
|
||||
}
|
||||
}
|
||||
PatternConstraintKind::Singleton(_) | PatternConstraintKind::Unsupported => {
|
||||
Truthiness::Ambiguous
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
VisibilityConstraintRef::Negated(visibility_constraint_id) => static_visibility(
|
||||
db,
|
||||
all_constraints,
|
||||
all_visibility_constraints,
|
||||
*visibility_constraint_id,
|
||||
)
|
||||
.negate(),
|
||||
VisibilityConstraintRef::None => Truthiness::AlwaysTrue,
|
||||
VisibilityConstraintRef::And(lhs_id, rhs_id) => {
|
||||
let lhs = static_visibility(db, all_constraints, all_visibility_constraints, *lhs_id);
|
||||
let rhs = static_visibility(db, all_constraints, all_visibility_constraints, *rhs_id);
|
||||
|
||||
if lhs == Truthiness::AlwaysFalse || rhs == Truthiness::AlwaysFalse {
|
||||
Truthiness::AlwaysFalse
|
||||
} else if lhs == Truthiness::AlwaysTrue && rhs == Truthiness::AlwaysTrue {
|
||||
Truthiness::AlwaysTrue
|
||||
} else {
|
||||
Truthiness::Ambiguous
|
||||
}
|
||||
}
|
||||
VisibilityConstraintRef::Or(lhs_id, rhs_id) => {
|
||||
let lhs = static_visibility(db, all_constraints, all_visibility_constraints, *lhs_id);
|
||||
let rhs = static_visibility(db, all_constraints, all_visibility_constraints, *rhs_id);
|
||||
|
||||
if lhs == Truthiness::AlwaysFalse && rhs == Truthiness::AlwaysFalse {
|
||||
Truthiness::AlwaysFalse
|
||||
} else if lhs == Truthiness::AlwaysTrue || rhs == Truthiness::AlwaysTrue {
|
||||
Truthiness::AlwaysTrue
|
||||
} else {
|
||||
Truthiness::Ambiguous
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user