diff --git a/crates/red_knot_python_semantic/src/semantic_index.rs b/crates/red_knot_python_semantic/src/semantic_index.rs index 263c4cfddc351..ce975fcb94a95 100644 --- a/crates/red_knot_python_semantic/src/semantic_index.rs +++ b/crates/red_knot_python_semantic/src/semantic_index.rs @@ -32,7 +32,7 @@ mod use_def; pub(crate) use self::use_def::{ BindingWithConstraints, BindingWithConstraintsIterator, DeclarationWithConstraint, - DeclarationsIterator, ScopedVisibilityConstraintId, + DeclarationsIterator, }; type SymbolMap = hashbrown::HashMap; diff --git a/crates/red_knot_python_semantic/src/semantic_index/builder.rs b/crates/red_knot_python_semantic/src/semantic_index/builder.rs index fc393e9a8bba5..ca75b22379ac3 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/builder.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/builder.rs @@ -25,12 +25,10 @@ use crate::semantic_index::symbol::{ FileScopeId, NodeWithScopeKey, NodeWithScopeRef, Scope, ScopeId, ScopeKind, ScopedSymbolId, SymbolTableBuilder, }; -use crate::semantic_index::use_def::{ - FlowSnapshot, ScopedConstraintId, ScopedVisibilityConstraintId, UseDefMapBuilder, -}; +use crate::semantic_index::use_def::{FlowSnapshot, ScopedConstraintId, UseDefMapBuilder}; use crate::semantic_index::SemanticIndex; use crate::unpack::{Unpack, UnpackValue}; -use crate::visibility_constraints::VisibilityConstraint; +use crate::visibility_constraints::{ScopedVisibilityConstraintId, VisibilityConstraintsBuilder}; use crate::Db; use super::constraint::{Constraint, ConstraintNode, PatternConstraint}; @@ -232,6 +230,11 @@ impl<'db> SemanticIndexBuilder<'db> { &self.use_def_maps[scope_id] } + fn current_visibility_constraints_mut(&mut self) -> &mut VisibilityConstraintsBuilder<'db> { + let scope_id = self.current_scope(); + &mut self.use_def_maps[scope_id].visibility_constraints + } + fn current_ast_ids(&mut self) -> &mut AstIdsBuilder { let scope_id = self.current_scope(); &mut self.ast_ids[scope_id] @@ -367,21 +370,11 @@ impl<'db> SemanticIndexBuilder<'db> { id } - /// Adds a new visibility constraint, but does not record it. Returns the constraint ID - /// for later recording using [`SemanticIndexBuilder::record_visibility_constraint_id`]. - fn add_visibility_constraint( - &mut self, - constraint: VisibilityConstraint<'db>, - ) -> ScopedVisibilityConstraintId { - self.current_use_def_map_mut() - .add_visibility_constraint(constraint) - } - /// Records a previously added visibility constraint by applying it to all live bindings /// and declarations. fn record_visibility_constraint_id(&mut self, constraint: ScopedVisibilityConstraintId) { self.current_use_def_map_mut() - .record_visibility_constraint_id(constraint); + .record_visibility_constraint(constraint); } /// Negates the given visibility constraint and then adds it to all live bindings and declarations. @@ -389,8 +382,11 @@ impl<'db> SemanticIndexBuilder<'db> { &mut self, constraint: ScopedVisibilityConstraintId, ) -> ScopedVisibilityConstraintId { - self.current_use_def_map_mut() - .record_visibility_constraint(VisibilityConstraint::VisibleIfNot(constraint)) + let id = self + .current_visibility_constraints_mut() + .add_not_constraint(constraint); + self.record_visibility_constraint_id(id); + id } /// Records a visibility constraint by applying it to all live bindings and declarations. @@ -398,8 +394,11 @@ impl<'db> SemanticIndexBuilder<'db> { &mut self, constraint: Constraint<'db>, ) -> ScopedVisibilityConstraintId { - self.current_use_def_map_mut() - .record_visibility_constraint(VisibilityConstraint::VisibleIf(constraint, 0)) + let id = self + .current_visibility_constraints_mut() + .add_atom(constraint, 0); + self.record_visibility_constraint_id(id); + id } /// Records that all remaining statements in the current block are unreachable, and therefore @@ -408,10 +407,10 @@ impl<'db> SemanticIndexBuilder<'db> { self.current_use_def_map_mut().mark_unreachable(); } - /// Records a [`VisibilityConstraint::Ambiguous`] constraint. - fn record_ambiguous_visibility(&mut self) -> ScopedVisibilityConstraintId { + /// Records a visibility constraint that always evaluates to "ambiguous". + fn record_ambiguous_visibility(&mut self) { self.current_use_def_map_mut() - .record_visibility_constraint(VisibilityConstraint::Ambiguous) + .record_visibility_constraint(ScopedVisibilityConstraintId::AMBIGUOUS); } /// Simplifies (resets) visibility constraints on all live bindings and declarations that did @@ -1091,10 +1090,12 @@ where // We need multiple copies of the visibility constraint for the while condition, // since we need to model situations where the first evaluation of the condition // returns True, but a later evaluation returns False. - let first_vis_constraint_id = - self.add_visibility_constraint(VisibilityConstraint::VisibleIf(constraint, 0)); - let later_vis_constraint_id = - self.add_visibility_constraint(VisibilityConstraint::VisibleIf(constraint, 1)); + let first_vis_constraint_id = self + .current_visibility_constraints_mut() + .add_atom(constraint, 0); + let later_vis_constraint_id = self + .current_visibility_constraints_mut() + .add_atom(constraint, 1); // Save aside any break states from an outer loop let saved_break_states = std::mem::take(&mut self.loop_break_states); @@ -1665,9 +1666,9 @@ where ast::BoolOp::And => (constraint, self.add_constraint(constraint)), ast::BoolOp::Or => self.add_negated_constraint(constraint), }; - let visibility_constraint = self.add_visibility_constraint( - VisibilityConstraint::VisibleIf(constraint, 0), - ); + let visibility_constraint = self + .current_visibility_constraints_mut() + .add_atom(constraint, 0); let after_expr = self.flow_snapshot(); diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs index 04120f8a67fa9..861a4c3132bd4 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs @@ -255,16 +255,18 @@ //! snapshot, and merging a snapshot into the current state. The logic using these methods lives in //! [`SemanticIndexBuilder`](crate::semantic_index::builder::SemanticIndexBuilder), e.g. where it //! visits a `StmtIf` node. +pub(crate) use self::symbol_state::ScopedConstraintId; use self::symbol_state::{ BindingIdWithConstraintsIterator, ConstraintIdIterator, DeclarationIdIterator, 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::semantic_index::use_def::symbol_state::DeclarationIdWithConstraint; -use crate::visibility_constraints::{VisibilityConstraint, VisibilityConstraints}; +use crate::visibility_constraints::{ + ScopedVisibilityConstraintId, VisibilityConstraints, VisibilityConstraintsBuilder, +}; use ruff_index::IndexVec; use rustc_hash::FxHashMap; @@ -285,7 +287,7 @@ pub(crate) struct UseDefMap<'db> { /// Array of [`Constraint`] in this scope. all_constraints: AllConstraints<'db>, - /// Array of [`VisibilityConstraint`]s in this scope. + /// Array of visibility constraints in this scope. visibility_constraints: VisibilityConstraints<'db>, /// [`SymbolBindings`] reaching a [`ScopedUseId`]. @@ -487,8 +489,8 @@ pub(super) struct UseDefMapBuilder<'db> { /// Append-only array of [`Constraint`]. all_constraints: AllConstraints<'db>, - /// Append-only array of [`VisibilityConstraint`]. - visibility_constraints: VisibilityConstraints<'db>, + /// Builder of visibility constraints. + pub(super) visibility_constraints: VisibilityConstraintsBuilder<'db>, /// A constraint which describes the visibility of the unbound/undeclared state, i.e. /// whether or not the start of the scope is visible. This is important for cases like @@ -513,7 +515,7 @@ impl Default for UseDefMapBuilder<'_> { Self { all_definitions: IndexVec::from_iter([None]), all_constraints: IndexVec::new(), - visibility_constraints: VisibilityConstraints::default(), + visibility_constraints: VisibilityConstraintsBuilder::default(), scope_start_visibility: ScopedVisibilityConstraintId::ALWAYS_TRUE, bindings_by_use: IndexVec::new(), definitions_by_definition: FxHashMap::default(), @@ -561,35 +563,18 @@ impl<'db> UseDefMapBuilder<'db> { new_constraint_id } - pub(super) fn add_visibility_constraint( - &mut self, - constraint: VisibilityConstraint<'db>, - ) -> ScopedVisibilityConstraintId { - self.visibility_constraints.add(constraint) - } - - pub(super) fn record_visibility_constraint_id( + pub(super) fn record_visibility_constraint( &mut self, constraint: ScopedVisibilityConstraintId, ) { for state in &mut self.symbol_states { state.record_visibility_constraint(&mut self.visibility_constraints, constraint); } - self.scope_start_visibility = self .visibility_constraints .add_and_constraint(self.scope_start_visibility, constraint); } - pub(super) fn record_visibility_constraint( - &mut self, - constraint: VisibilityConstraint<'db>, - ) -> ScopedVisibilityConstraintId { - let new_constraint_id = self.add_visibility_constraint(constraint); - self.record_visibility_constraint_id(new_constraint_id); - new_constraint_id - } - /// This method resets the visibility constraints for all symbols to a previous state /// *if* there have been no new declarations or bindings since then. Consider the /// following example: @@ -742,7 +727,7 @@ impl<'db> UseDefMapBuilder<'db> { UseDefMap { all_definitions: self.all_definitions, all_constraints: self.all_constraints, - visibility_constraints: self.visibility_constraints, + visibility_constraints: self.visibility_constraints.build(), bindings_by_use: self.bindings_by_use, public_symbols: self.symbol_states, definitions_by_definition: self.definitions_by_definition, diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs index 39ccd193cfc0d..4c0c72bc6226a 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs @@ -49,7 +49,8 @@ use ruff_index::newtype_index; use smallvec::SmallVec; use crate::semantic_index::use_def::bitset::{BitSet, BitSetIterator}; -use crate::semantic_index::use_def::VisibilityConstraints; +use crate::semantic_index::use_def::VisibilityConstraintsBuilder; +use crate::visibility_constraints::ScopedVisibilityConstraintId; /// A newtype-index for a definition in a particular scope. #[newtype_index] @@ -99,18 +100,6 @@ type ConstraintsPerBinding = SmallVec; /// Iterate over all constraints for a single binding. type ConstraintsIterator<'a> = std::slice::Iter<'a, Constraints>; -/// A newtype-index for a visibility constraint in a particular scope. -#[newtype_index] -pub(crate) struct ScopedVisibilityConstraintId; - -impl ScopedVisibilityConstraintId { - /// A special ID that is used for an "always true" / "always visible" constraint. - /// When we create a new [`VisibilityConstraints`] object, this constraint is always - /// present at index 0. - pub(crate) const ALWAYS_TRUE: ScopedVisibilityConstraintId = - ScopedVisibilityConstraintId::from_u32(0); -} - const INLINE_VISIBILITY_CONSTRAINTS: usize = 4; type InlineVisibilityConstraintsArray = [ScopedVisibilityConstraintId; INLINE_VISIBILITY_CONSTRAINTS]; @@ -164,7 +153,7 @@ impl SymbolDeclarations { /// Add given visibility constraint to all live declarations. pub(super) fn record_visibility_constraint( &mut self, - visibility_constraints: &mut VisibilityConstraints, + visibility_constraints: &mut VisibilityConstraintsBuilder, constraint: ScopedVisibilityConstraintId, ) { for existing in &mut self.visibility_constraints { @@ -180,7 +169,7 @@ impl SymbolDeclarations { } } - fn merge(&mut self, b: Self, visibility_constraints: &mut VisibilityConstraints) { + fn merge(&mut self, b: Self, visibility_constraints: &mut VisibilityConstraintsBuilder) { let a = std::mem::take(self); self.live_declarations = a.live_declarations.clone(); self.live_declarations.union(&b.live_declarations); @@ -270,7 +259,7 @@ impl SymbolBindings { /// Add given visibility constraint to all live bindings. pub(super) fn record_visibility_constraint( &mut self, - visibility_constraints: &mut VisibilityConstraints, + visibility_constraints: &mut VisibilityConstraintsBuilder, constraint: ScopedVisibilityConstraintId, ) { for existing in &mut self.visibility_constraints { @@ -287,7 +276,7 @@ impl SymbolBindings { } } - fn merge(&mut self, mut b: Self, visibility_constraints: &mut VisibilityConstraints) { + fn merge(&mut self, mut b: Self, visibility_constraints: &mut VisibilityConstraintsBuilder) { let mut a = std::mem::take(self); self.live_bindings = a.live_bindings.clone(); self.live_bindings.union(&b.live_bindings); @@ -373,7 +362,7 @@ impl SymbolState { /// Add given visibility constraint to all live bindings. pub(super) fn record_visibility_constraint( &mut self, - visibility_constraints: &mut VisibilityConstraints, + visibility_constraints: &mut VisibilityConstraintsBuilder, constraint: ScopedVisibilityConstraintId, ) { self.bindings @@ -401,7 +390,7 @@ impl SymbolState { pub(super) fn merge( &mut self, b: SymbolState, - visibility_constraints: &mut VisibilityConstraints, + visibility_constraints: &mut VisibilityConstraintsBuilder, ) { self.bindings.merge(b.bindings, visibility_constraints); self.declarations @@ -584,7 +573,7 @@ mod tests { #[test] fn merge() { - let mut visibility_constraints = VisibilityConstraints::default(); + let mut visibility_constraints = VisibilityConstraintsBuilder::default(); // merging the same definition with the same constraint keeps the constraint let mut sym1a = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); @@ -655,7 +644,7 @@ mod tests { #[test] fn record_declaration_merge() { - let mut visibility_constraints = VisibilityConstraints::default(); + let mut visibility_constraints = VisibilityConstraintsBuilder::default(); let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); sym.record_declaration(ScopedDefinitionId::from_u32(1)); @@ -669,7 +658,7 @@ mod tests { #[test] fn record_declaration_merge_partial_undeclared() { - let mut visibility_constraints = VisibilityConstraints::default(); + let mut visibility_constraints = VisibilityConstraintsBuilder::default(); let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); sym.record_declaration(ScopedDefinitionId::from_u32(1)); diff --git a/crates/red_knot_python_semantic/src/visibility_constraints.rs b/crates/red_knot_python_semantic/src/visibility_constraints.rs index 1120bf0c8b2fc..6c0126e50dd91 100644 --- a/crates/red_knot_python_semantic/src/visibility_constraints.rs +++ b/crates/red_knot_python_semantic/src/visibility_constraints.rs @@ -122,7 +122,7 @@ //! //! ### Explicit ambiguity //! -//! In some cases, we explicitly add a `VisibilityConstraint::Ambiguous` constraint to all bindings +//! In some cases, we explicitly add an “ambiguous” constraint to all bindings //! in a certain control flow path. We do this when branching on something that we can not (or //! intentionally do not want to) analyze statically. `for` loops are one example: //! ```py @@ -150,9 +150,8 @@ //! //! [Kleene]: -use ruff_index::IndexVec; +use ruff_index::{newtype_index, IndexVec}; -use crate::semantic_index::ScopedVisibilityConstraintId; use crate::semantic_index::{ ast_ids::HasScopedExpressionId, constraint::{Constraint, ConstraintNode, PatternConstraintKind}, @@ -184,8 +183,12 @@ const MAX_RECURSION_DEPTH: usize = 24; /// for a particular [`Constraint`], if your formula needs to consider how a particular runtime /// property might be different at different points in the execution of the program. #[derive(Clone, Debug, PartialEq, Eq)] -pub(crate) enum VisibilityConstraint<'db> { +pub(crate) struct VisibilityConstraint<'db>(VisibilityConstraintInner<'db>); + +#[derive(Clone, Debug, PartialEq, Eq)] +pub(crate) enum VisibilityConstraintInner<'db> { AlwaysTrue, + AlwaysFalse, Ambiguous, VisibleIf(Constraint<'db>, u8), VisibleIfNot(ScopedVisibilityConstraintId), @@ -193,25 +196,84 @@ pub(crate) enum VisibilityConstraint<'db> { KleeneOr(ScopedVisibilityConstraintId, ScopedVisibilityConstraintId), } +/// A newtype-index for a visibility constraint in a particular scope. +#[newtype_index] +pub(crate) struct ScopedVisibilityConstraintId; + +impl ScopedVisibilityConstraintId { + /// A special ID that is used for an "always true" / "always visible" constraint. + /// When we create a new [`VisibilityConstraints`] object, this constraint is always + /// present at index 0. + pub(crate) const ALWAYS_TRUE: ScopedVisibilityConstraintId = + ScopedVisibilityConstraintId::from_u32(0); + + /// A special ID that is used for an "always false" / "never visible" constraint. + /// When we create a new [`VisibilityConstraints`] object, this constraint is always + /// present at index 1. + pub(crate) const ALWAYS_FALSE: ScopedVisibilityConstraintId = + ScopedVisibilityConstraintId::from_u32(1); + + /// A special ID that is used for an ambiguous constraint. + /// When we create a new [`VisibilityConstraints`] object, this constraint is always + /// present at index 2. + pub(crate) const AMBIGUOUS: ScopedVisibilityConstraintId = + ScopedVisibilityConstraintId::from_u32(2); +} + #[derive(Debug, PartialEq, Eq)] pub(crate) struct VisibilityConstraints<'db> { constraints: IndexVec>, } -impl Default for VisibilityConstraints<'_> { +#[derive(Debug, PartialEq, Eq)] +pub(crate) struct VisibilityConstraintsBuilder<'db> { + constraints: IndexVec>, +} + +impl Default for VisibilityConstraintsBuilder<'_> { fn default() -> Self { Self { - constraints: IndexVec::from_iter([VisibilityConstraint::AlwaysTrue]), + constraints: IndexVec::from_iter([ + VisibilityConstraint(VisibilityConstraintInner::AlwaysTrue), + VisibilityConstraint(VisibilityConstraintInner::AlwaysFalse), + VisibilityConstraint(VisibilityConstraintInner::Ambiguous), + ]), } } } -impl<'db> VisibilityConstraints<'db> { - pub(crate) fn add( +impl<'db> VisibilityConstraintsBuilder<'db> { + pub(crate) fn build(self) -> VisibilityConstraints<'db> { + VisibilityConstraints { + constraints: self.constraints, + } + } + + fn add(&mut self, constraint: VisibilityConstraintInner<'db>) -> ScopedVisibilityConstraintId { + self.constraints.push(VisibilityConstraint(constraint)) + } + + pub(crate) fn add_atom( + &mut self, + constraint: Constraint<'db>, + copy: u8, + ) -> ScopedVisibilityConstraintId { + self.add(VisibilityConstraintInner::VisibleIf(constraint, copy)) + } + + pub(crate) fn add_not_constraint( &mut self, - constraint: VisibilityConstraint<'db>, + a: ScopedVisibilityConstraintId, ) -> ScopedVisibilityConstraintId { - self.constraints.push(constraint) + if a == ScopedVisibilityConstraintId::ALWAYS_FALSE { + ScopedVisibilityConstraintId::ALWAYS_TRUE + } else if a == ScopedVisibilityConstraintId::ALWAYS_TRUE { + ScopedVisibilityConstraintId::ALWAYS_FALSE + } else if a == ScopedVisibilityConstraintId::AMBIGUOUS { + ScopedVisibilityConstraintId::AMBIGUOUS + } else { + self.add(VisibilityConstraintInner::VisibleIfNot(a)) + } } pub(crate) fn add_or_constraint( @@ -219,14 +281,23 @@ impl<'db> VisibilityConstraints<'db> { a: ScopedVisibilityConstraintId, b: ScopedVisibilityConstraintId, ) -> ScopedVisibilityConstraintId { + if a == ScopedVisibilityConstraintId::ALWAYS_TRUE + || b == ScopedVisibilityConstraintId::ALWAYS_TRUE + { + return ScopedVisibilityConstraintId::ALWAYS_TRUE; + } else if a == ScopedVisibilityConstraintId::ALWAYS_FALSE { + return b; + } else if b == ScopedVisibilityConstraintId::ALWAYS_FALSE { + return a; + } match (&self.constraints[a], &self.constraints[b]) { - (_, VisibilityConstraint::VisibleIfNot(id)) if a == *id => { + (_, VisibilityConstraint(VisibilityConstraintInner::VisibleIfNot(id))) if a == *id => { ScopedVisibilityConstraintId::ALWAYS_TRUE } - (VisibilityConstraint::VisibleIfNot(id), _) if *id == b => { + (VisibilityConstraint(VisibilityConstraintInner::VisibleIfNot(id)), _) if *id == b => { ScopedVisibilityConstraintId::ALWAYS_TRUE } - _ => self.add(VisibilityConstraint::KleeneOr(a, b)), + _ => self.add(VisibilityConstraintInner::KleeneOr(a, b)), } } @@ -235,22 +306,28 @@ impl<'db> VisibilityConstraints<'db> { a: ScopedVisibilityConstraintId, b: ScopedVisibilityConstraintId, ) -> ScopedVisibilityConstraintId { - if a == ScopedVisibilityConstraintId::ALWAYS_TRUE { + if a == ScopedVisibilityConstraintId::ALWAYS_FALSE + || b == ScopedVisibilityConstraintId::ALWAYS_FALSE + { + return ScopedVisibilityConstraintId::ALWAYS_FALSE; + } else if a == ScopedVisibilityConstraintId::ALWAYS_TRUE { return b; } else if b == ScopedVisibilityConstraintId::ALWAYS_TRUE { return a; } match (&self.constraints[a], &self.constraints[b]) { - (_, VisibilityConstraint::VisibleIfNot(id)) if a == *id => self.add( - VisibilityConstraint::VisibleIfNot(ScopedVisibilityConstraintId::ALWAYS_TRUE), - ), - (VisibilityConstraint::VisibleIfNot(id), _) if *id == b => self.add( - VisibilityConstraint::VisibleIfNot(ScopedVisibilityConstraintId::ALWAYS_TRUE), - ), - _ => self.add(VisibilityConstraint::KleeneAnd(a, b)), + (_, VisibilityConstraint(VisibilityConstraintInner::VisibleIfNot(id))) if a == *id => { + ScopedVisibilityConstraintId::ALWAYS_FALSE + } + (VisibilityConstraint(VisibilityConstraintInner::VisibleIfNot(id)), _) if *id == b => { + ScopedVisibilityConstraintId::ALWAYS_FALSE + } + _ => self.add(VisibilityConstraintInner::KleeneAnd(a, b)), } } +} +impl<'db> VisibilityConstraints<'db> { /// Analyze the statically known visibility for a given visibility constraint. pub(crate) fn evaluate(&self, db: &'db dyn Db, id: ScopedVisibilityConstraintId) -> Truthiness { self.evaluate_impl(db, id, MAX_RECURSION_DEPTH) @@ -266,15 +343,18 @@ impl<'db> VisibilityConstraints<'db> { return Truthiness::Ambiguous; } - let visibility_constraint = &self.constraints[id]; + let VisibilityConstraint(visibility_constraint) = &self.constraints[id]; match visibility_constraint { - VisibilityConstraint::AlwaysTrue => Truthiness::AlwaysTrue, - VisibilityConstraint::Ambiguous => Truthiness::Ambiguous, - VisibilityConstraint::VisibleIf(constraint, _) => Self::analyze_single(db, constraint), - VisibilityConstraint::VisibleIfNot(negated) => { + VisibilityConstraintInner::AlwaysTrue => Truthiness::AlwaysTrue, + VisibilityConstraintInner::AlwaysFalse => Truthiness::AlwaysFalse, + VisibilityConstraintInner::Ambiguous => Truthiness::Ambiguous, + VisibilityConstraintInner::VisibleIf(constraint, _) => { + Self::analyze_single(db, constraint) + } + VisibilityConstraintInner::VisibleIfNot(negated) => { self.evaluate_impl(db, *negated, max_depth - 1).negate() } - VisibilityConstraint::KleeneAnd(lhs, rhs) => { + VisibilityConstraintInner::KleeneAnd(lhs, rhs) => { let lhs = self.evaluate_impl(db, *lhs, max_depth - 1); if lhs == Truthiness::AlwaysFalse { @@ -291,7 +371,7 @@ impl<'db> VisibilityConstraints<'db> { Truthiness::Ambiguous } } - VisibilityConstraint::KleeneOr(lhs_id, rhs_id) => { + VisibilityConstraintInner::KleeneOr(lhs_id, rhs_id) => { let lhs = self.evaluate_impl(db, *lhs_id, max_depth - 1); if lhs == Truthiness::AlwaysTrue {