1use crate::Program;
11use ruff_db::PythonFile;
12use ruff_db::files::File;
13use ruff_index::{FrozenIndexVec, Idx, IndexVec};
14use ruff_python_ast::{Singleton, name::Name};
15
16use crate::ProgramFile;
17use crate::ast_ids::ExpressionNodeKey;
18use crate::db::Db;
19use crate::expression::Expression;
20use crate::global_scope;
21use crate::reachability_constraints::ScopedReachabilityConstraintId;
22use crate::scope::{FileScopeId, ScopeId};
23use crate::symbol::ScopedSymbolId;
24
25#[derive(Clone, Debug, Copy, PartialOrd, Ord, PartialEq, Eq, Hash, get_size2::GetSize)]
27pub struct ScopedPredicateId(u32);
28
29impl ScopedPredicateId {
30 pub(crate) const ALWAYS_TRUE: ScopedPredicateId = ScopedPredicateId(0xffff_ffff);
32
33 pub(crate) const ALWAYS_FALSE: ScopedPredicateId = ScopedPredicateId(0xffff_fffe);
35
36 const SMALLEST_TERMINAL: ScopedPredicateId = Self::ALWAYS_FALSE;
37
38 fn is_terminal(self) -> bool {
39 self >= Self::SMALLEST_TERMINAL
40 }
41}
42
43impl Idx for ScopedPredicateId {
44 #[inline]
45 fn new(value: usize) -> Self {
46 assert!(value <= (Self::SMALLEST_TERMINAL.0 as usize));
47 #[expect(clippy::cast_possible_truncation)]
48 Self(value as u32)
49 }
50
51 #[inline]
52 fn index(self) -> usize {
53 debug_assert!(!self.is_terminal());
54 self.0 as usize
55 }
56}
57
58pub type Predicates<'db> = FrozenIndexVec<ScopedPredicateId, Predicate<'db>>;
60
61#[derive(Debug, Default)]
62pub(crate) struct PredicatesBuilder<'db> {
63 predicates: IndexVec<ScopedPredicateId, Predicate<'db>>,
64}
65
66impl<'db> PredicatesBuilder<'db> {
67 pub(crate) fn add_predicate(&mut self, predicate: Predicate<'db>) -> ScopedPredicateId {
71 self.predicates.push(predicate)
72 }
73
74 pub(crate) fn build(self) -> Predicates<'db> {
75 self.predicates.into()
76 }
77}
78
79#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, get_size2::GetSize, salsa::SalsaValue)]
80pub struct Predicate<'db> {
81 pub node: PredicateNode<'db>,
82 pub is_positive: bool,
83}
84
85#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, get_size2::GetSize)]
86pub(crate) enum PredicateOrLiteral<'db> {
87 Literal(bool),
88 Predicate(Predicate<'db>),
89}
90
91impl PredicateOrLiteral<'_> {
92 pub(crate) fn negated(self) -> Self {
93 match self {
94 PredicateOrLiteral::Literal(value) => PredicateOrLiteral::Literal(!value),
95 PredicateOrLiteral::Predicate(Predicate { node, is_positive }) => {
96 PredicateOrLiteral::Predicate(Predicate {
97 node,
98 is_positive: !is_positive,
99 })
100 }
101 }
102 }
103}
104
105#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, get_size2::GetSize, salsa::SalsaValue)]
106pub struct CallableAndCallExpr<'db> {
107 pub callable: Expression<'db>,
108 pub call_expr: Expression<'db>,
109 pub is_await: bool,
113}
114
115#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, get_size2::GetSize, salsa::SalsaValue)]
116pub enum PredicateNode<'db> {
117 Expression(Expression<'db>),
118 ContextManagerSuppresses {
123 expression: Expression<'db>,
124 is_async: bool,
125 },
126 FinallyNormalPathImpossible {
132 scope: ScopeId<'db>,
133 continuation: ScopedReachabilityConstraintId,
134 },
135 IsNonTerminalCall(CallableAndCallExpr<'db>),
153 IsNonEmptyIterable(Expression<'db>),
158 Pattern(PatternPredicate<'db>),
159 OrPatternAlternative(ScopeId<'db>),
163 SubjectElementPattern(SubjectElementPatternPredicate<'db>),
164 StarImportPlaceholder(StarImportPlaceholderPredicate<'db>),
165}
166
167#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, get_size2::GetSize, salsa::SalsaValue)]
172pub struct SubjectElementPatternPredicate<'db> {
173 pub pattern: PatternPredicate<'db>,
174 pub target: ExpressionNodeKey,
175}
176
177#[derive(Debug, Clone, Hash, PartialEq, get_size2::GetSize, salsa::SalsaValue)]
179pub struct SequencePatternPredicateKind<'db> {
180 pub patterns: Box<[PatternPredicateKind<'db>]>,
181}
182
183impl<'db> SequencePatternPredicateKind<'db> {
184 pub fn is_irrefutable(&self) -> bool {
187 matches!(self.patterns.as_ref(), [PatternPredicateKind::Star(_)])
188 }
189
190 pub fn split_around_star(
192 &self,
193 ) -> Option<(&[PatternPredicateKind<'db>], &[PatternPredicateKind<'db>])> {
194 let star_index = self
195 .patterns
196 .iter()
197 .position(|pattern| matches!(pattern, PatternPredicateKind::Star(_)))?;
198 let (prefix, star_and_suffix) = self.patterns.split_at(star_index);
199 Some((prefix, &star_and_suffix[1..]))
200 }
201}
202
203#[derive(Debug, Clone, Hash, PartialEq, get_size2::GetSize, salsa::SalsaValue)]
205pub struct ClassPatternPredicateKind<'db> {
206 pub class: Expression<'db>,
207 pub positional: Box<[PatternPredicateKind<'db>]>,
208 pub keywords: Box<[ClassPatternKeywordPredicateKind<'db>]>,
209}
210
211impl ClassPatternPredicateKind<'_> {
212 pub fn is_empty(&self) -> bool {
213 self.positional.is_empty() && self.keywords.is_empty()
214 }
215}
216
217#[derive(Debug, Clone, Hash, PartialEq, get_size2::GetSize, salsa::SalsaValue)]
218pub struct ClassPatternKeywordPredicateKind<'db> {
219 pub attr: Name,
220 pub pattern: PatternPredicateKind<'db>,
221}
222
223#[derive(Debug, Clone, Hash, PartialEq, get_size2::GetSize, salsa::SalsaValue)]
225pub struct MappingPatternPredicateKind<'db> {
226 pub entries: Box<[MappingPatternEntryPredicateKind<'db>]>,
227 pub rest: Option<Name>,
228}
229
230impl MappingPatternPredicateKind<'_> {
231 pub fn is_irrefutable(&self) -> bool {
232 self.entries.is_empty()
233 }
234}
235
236#[derive(Debug, Clone, Hash, PartialEq, get_size2::GetSize, salsa::SalsaValue)]
237pub struct MappingPatternEntryPredicateKind<'db> {
238 pub key: Expression<'db>,
239 pub pattern: PatternPredicateKind<'db>,
240}
241
242#[derive(Debug, Clone, Hash, PartialEq, get_size2::GetSize, salsa::SalsaValue)]
245pub enum PatternPredicateKind<'db> {
246 Singleton(Singleton),
247 Value(Expression<'db>),
248 Or(Box<[PatternPredicateKind<'db>]>),
249 Class(ClassPatternPredicateKind<'db>),
250 Mapping(MappingPatternPredicateKind<'db>),
251 Sequence(SequencePatternPredicateKind<'db>),
252 As(Option<Box<PatternPredicateKind<'db>>>, Option<Name>),
253 Star(Option<Name>),
254}
255
256#[salsa::tracked(debug, heap_size=ruff_memory_usage::heap_size)]
257pub struct PatternPredicate<'db> {
258 #[returns(copy)]
259 pub program_file: ProgramFile<'db>,
260
261 #[returns(copy)]
262 pub file_scope: FileScopeId,
263
264 #[returns(copy)]
265 pub subject: Expression<'db>,
266
267 #[returns(ref)]
268 pub kind: PatternPredicateKind<'db>,
269
270 #[returns(copy)]
271 pub guard: Option<Expression<'db>>,
272
273 #[returns(as_deref)]
275 pub previous_predicate: Option<Box<PatternPredicate<'db>>>,
276}
277
278impl get_size2::GetSize for PatternPredicate<'_> {}
280
281impl<'db> PatternPredicate<'db> {
282 pub fn file(self, db: &'db dyn Db) -> File {
283 self.program_file(db).file(db)
284 }
285
286 pub fn python_file(self, db: &'db dyn Db) -> PythonFile<'db> {
287 self.program_file(db).python_file(db)
288 }
289
290 pub fn scope(self, db: &'db dyn Db) -> ScopeId<'db> {
291 self.file_scope(db).to_scope_id(db, self.program_file(db))
292 }
293
294 pub fn program(self, db: &'db dyn Db) -> Program<'db> {
295 self.scope(db).program(db)
296 }
297}
298
299#[salsa::tracked(debug, heap_size=ruff_memory_usage::heap_size)]
340pub struct StarImportPlaceholderPredicate<'db> {
341 #[returns(copy)]
342 pub importing_file: ProgramFile<'db>,
343
344 #[returns(copy)]
354 pub symbol_id: ScopedSymbolId,
355
356 #[returns(copy)]
357 pub referenced_file: ProgramFile<'db>,
358}
359
360impl get_size2::GetSize for StarImportPlaceholderPredicate<'_> {}
362
363impl<'db> StarImportPlaceholderPredicate<'db> {
364 pub fn scope(self, db: &'db dyn Db) -> ScopeId<'db> {
365 global_scope(db, self.importing_file(db))
368 }
369}
370
371impl<'db> From<StarImportPlaceholderPredicate<'db>> for PredicateOrLiteral<'db> {
372 fn from(predicate: StarImportPlaceholderPredicate<'db>) -> Self {
373 PredicateOrLiteral::Predicate(Predicate {
374 node: PredicateNode::StarImportPlaceholder(predicate),
375 is_positive: true,
376 })
377 }
378}