use crate::Program;
use ruff_db::PythonFile;
use ruff_db::files::File;
use ruff_index::{FrozenIndexVec, Idx, IndexVec};
use ruff_python_ast::{Singleton, name::Name};
use crate::ProgramFile;
use crate::ast_ids::ExpressionNodeKey;
use crate::db::Db;
use crate::expression::Expression;
use crate::global_scope;
use crate::reachability_constraints::ScopedReachabilityConstraintId;
use crate::scope::{FileScopeId, ScopeId};
use crate::symbol::ScopedSymbolId;
#[derive(Clone, Debug, Copy, PartialOrd, Ord, PartialEq, Eq, Hash, get_size2::GetSize)]
pub struct ScopedPredicateId(u32);
impl ScopedPredicateId {
pub(crate) const ALWAYS_TRUE: ScopedPredicateId = ScopedPredicateId(0xffff_ffff);
pub(crate) const ALWAYS_FALSE: ScopedPredicateId = ScopedPredicateId(0xffff_fffe);
const SMALLEST_TERMINAL: ScopedPredicateId = Self::ALWAYS_FALSE;
fn is_terminal(self) -> bool {
self >= Self::SMALLEST_TERMINAL
}
}
impl Idx for ScopedPredicateId {
#[inline]
fn new(value: usize) -> Self {
assert!(value <= (Self::SMALLEST_TERMINAL.0 as usize));
#[expect(clippy::cast_possible_truncation)]
Self(value as u32)
}
#[inline]
fn index(self) -> usize {
debug_assert!(!self.is_terminal());
self.0 as usize
}
}
pub type Predicates<'db> = FrozenIndexVec<ScopedPredicateId, Predicate<'db>>;
#[derive(Debug, Default)]
pub(crate) struct PredicatesBuilder<'db> {
predicates: IndexVec<ScopedPredicateId, Predicate<'db>>,
}
impl<'db> PredicatesBuilder<'db> {
pub(crate) fn add_predicate(&mut self, predicate: Predicate<'db>) -> ScopedPredicateId {
self.predicates.push(predicate)
}
pub(crate) fn build(self) -> Predicates<'db> {
self.predicates.into()
}
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, get_size2::GetSize, salsa::SalsaValue)]
pub struct Predicate<'db> {
pub node: PredicateNode<'db>,
pub is_positive: bool,
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, get_size2::GetSize)]
pub(crate) enum PredicateOrLiteral<'db> {
Literal(bool),
Predicate(Predicate<'db>),
}
impl PredicateOrLiteral<'_> {
pub(crate) fn negated(self) -> Self {
match self {
PredicateOrLiteral::Literal(value) => PredicateOrLiteral::Literal(!value),
PredicateOrLiteral::Predicate(Predicate { node, is_positive }) => {
PredicateOrLiteral::Predicate(Predicate {
node,
is_positive: !is_positive,
})
}
}
}
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, get_size2::GetSize, salsa::SalsaValue)]
pub struct CallableAndCallExpr<'db> {
pub callable: Expression<'db>,
pub call_expr: Expression<'db>,
pub is_await: bool,
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, get_size2::GetSize, salsa::SalsaValue)]
pub enum PredicateNode<'db> {
Expression(Expression<'db>),
ContextManagerSuppresses {
expression: Expression<'db>,
is_async: bool,
},
FinallyNormalPathImpossible {
scope: ScopeId<'db>,
continuation: ScopedReachabilityConstraintId,
},
IsNonTerminalCall(CallableAndCallExpr<'db>),
IsNonEmptyIterable(Expression<'db>),
Pattern(PatternPredicate<'db>),
OrPatternAlternative(ScopeId<'db>),
SubjectElementPattern(SubjectElementPatternPredicate<'db>),
StarImportPlaceholder(StarImportPlaceholderPredicate<'db>),
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, get_size2::GetSize, salsa::SalsaValue)]
pub struct SubjectElementPatternPredicate<'db> {
pub pattern: PatternPredicate<'db>,
pub target: ExpressionNodeKey,
}
#[derive(Debug, Clone, Hash, PartialEq, get_size2::GetSize, salsa::SalsaValue)]
pub struct SequencePatternPredicateKind<'db> {
pub patterns: Box<[PatternPredicateKind<'db>]>,
}
impl<'db> SequencePatternPredicateKind<'db> {
pub fn is_irrefutable(&self) -> bool {
matches!(self.patterns.as_ref(), [PatternPredicateKind::Star(_)])
}
pub fn split_around_star(
&self,
) -> Option<(&[PatternPredicateKind<'db>], &[PatternPredicateKind<'db>])> {
let star_index = self
.patterns
.iter()
.position(|pattern| matches!(pattern, PatternPredicateKind::Star(_)))?;
let (prefix, star_and_suffix) = self.patterns.split_at(star_index);
Some((prefix, &star_and_suffix[1..]))
}
}
#[derive(Debug, Clone, Hash, PartialEq, get_size2::GetSize, salsa::SalsaValue)]
pub struct ClassPatternPredicateKind<'db> {
pub class: Expression<'db>,
pub positional: Box<[PatternPredicateKind<'db>]>,
pub keywords: Box<[ClassPatternKeywordPredicateKind<'db>]>,
}
impl ClassPatternPredicateKind<'_> {
pub fn is_empty(&self) -> bool {
self.positional.is_empty() && self.keywords.is_empty()
}
}
#[derive(Debug, Clone, Hash, PartialEq, get_size2::GetSize, salsa::SalsaValue)]
pub struct ClassPatternKeywordPredicateKind<'db> {
pub attr: Name,
pub pattern: PatternPredicateKind<'db>,
}
#[derive(Debug, Clone, Hash, PartialEq, get_size2::GetSize, salsa::SalsaValue)]
pub struct MappingPatternPredicateKind<'db> {
pub entries: Box<[MappingPatternEntryPredicateKind<'db>]>,
pub rest: Option<Name>,
}
impl MappingPatternPredicateKind<'_> {
pub fn is_irrefutable(&self) -> bool {
self.entries.is_empty()
}
}
#[derive(Debug, Clone, Hash, PartialEq, get_size2::GetSize, salsa::SalsaValue)]
pub struct MappingPatternEntryPredicateKind<'db> {
pub key: Expression<'db>,
pub pattern: PatternPredicateKind<'db>,
}
#[derive(Debug, Clone, Hash, PartialEq, get_size2::GetSize, salsa::SalsaValue)]
pub enum PatternPredicateKind<'db> {
Singleton(Singleton),
Value(Expression<'db>),
Or(Box<[PatternPredicateKind<'db>]>),
Class(ClassPatternPredicateKind<'db>),
Mapping(MappingPatternPredicateKind<'db>),
Sequence(SequencePatternPredicateKind<'db>),
As(Option<Box<PatternPredicateKind<'db>>>, Option<Name>),
Star(Option<Name>),
}
#[salsa::tracked(debug, heap_size=ruff_memory_usage::heap_size)]
pub struct PatternPredicate<'db> {
#[returns(copy)]
pub program_file: ProgramFile<'db>,
#[returns(copy)]
pub file_scope: FileScopeId,
#[returns(copy)]
pub subject: Expression<'db>,
#[returns(ref)]
pub kind: PatternPredicateKind<'db>,
#[returns(copy)]
pub guard: Option<Expression<'db>>,
#[returns(as_deref)]
pub previous_predicate: Option<Box<PatternPredicate<'db>>>,
}
impl get_size2::GetSize for PatternPredicate<'_> {}
impl<'db> PatternPredicate<'db> {
pub fn file(self, db: &'db dyn Db) -> File {
self.program_file(db).file(db)
}
pub fn python_file(self, db: &'db dyn Db) -> PythonFile<'db> {
self.program_file(db).python_file(db)
}
pub fn scope(self, db: &'db dyn Db) -> ScopeId<'db> {
self.file_scope(db).to_scope_id(db, self.program_file(db))
}
pub fn program(self, db: &'db dyn Db) -> Program<'db> {
self.scope(db).program(db)
}
}
#[salsa::tracked(debug, heap_size=ruff_memory_usage::heap_size)]
pub struct StarImportPlaceholderPredicate<'db> {
#[returns(copy)]
pub importing_file: ProgramFile<'db>,
#[returns(copy)]
pub symbol_id: ScopedSymbolId,
#[returns(copy)]
pub referenced_file: ProgramFile<'db>,
}
impl get_size2::GetSize for StarImportPlaceholderPredicate<'_> {}
impl<'db> StarImportPlaceholderPredicate<'db> {
pub fn scope(self, db: &'db dyn Db) -> ScopeId<'db> {
global_scope(db, self.importing_file(db))
}
}
impl<'db> From<StarImportPlaceholderPredicate<'db>> for PredicateOrLiteral<'db> {
fn from(predicate: StarImportPlaceholderPredicate<'db>) -> Self {
PredicateOrLiteral::Predicate(Predicate {
node: PredicateNode::StarImportPlaceholder(predicate),
is_positive: true,
})
}
}