[red-knot] Internal refactoring of visibility constraints API (#15913)
Some checks are pending
CI / cargo fuzz build (push) Blocked by required conditions
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / benchmarks (push) Blocked by required conditions

This extracts some pure refactoring noise from
https://github.com/astral-sh/ruff/pull/15861. This changes the API for
creating and evaluating visibility constraints, but does not change how
they are respresented internally. There should be no behavioral or
performance changes in this PR.

Changes:

- Hide the internal representation isn't changed, so that we can make
changes to it in #15861.
- Add a separate builder type for visibility constraints. (With TDDs, we
will have some additional builder state that we can throw away once
we're done constructing.)
- Remove a layer of helper methods from `UseDefMapBuilder`, making
`SemanticIndexBuilder` responsible for constructing whatever visibility
constraints it needs.
This commit is contained in:
Douglas Creager 2025-02-03 15:13:09 -05:00 committed by GitHub
parent 102c2eec12
commit 0529ad67d7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 160 additions and 105 deletions

View file

@ -32,7 +32,7 @@ mod use_def;
pub(crate) use self::use_def::{ pub(crate) use self::use_def::{
BindingWithConstraints, BindingWithConstraintsIterator, DeclarationWithConstraint, BindingWithConstraints, BindingWithConstraintsIterator, DeclarationWithConstraint,
DeclarationsIterator, ScopedVisibilityConstraintId, DeclarationsIterator,
}; };
type SymbolMap = hashbrown::HashMap<ScopedSymbolId, (), FxBuildHasher>; type SymbolMap = hashbrown::HashMap<ScopedSymbolId, (), FxBuildHasher>;

View file

@ -25,12 +25,10 @@ use crate::semantic_index::symbol::{
FileScopeId, NodeWithScopeKey, NodeWithScopeRef, Scope, ScopeId, ScopeKind, ScopedSymbolId, FileScopeId, NodeWithScopeKey, NodeWithScopeRef, Scope, ScopeId, ScopeKind, ScopedSymbolId,
SymbolTableBuilder, SymbolTableBuilder,
}; };
use crate::semantic_index::use_def::{ use crate::semantic_index::use_def::{FlowSnapshot, ScopedConstraintId, UseDefMapBuilder};
FlowSnapshot, ScopedConstraintId, ScopedVisibilityConstraintId, UseDefMapBuilder,
};
use crate::semantic_index::SemanticIndex; use crate::semantic_index::SemanticIndex;
use crate::unpack::{Unpack, UnpackValue}; use crate::unpack::{Unpack, UnpackValue};
use crate::visibility_constraints::VisibilityConstraint; use crate::visibility_constraints::{ScopedVisibilityConstraintId, VisibilityConstraintsBuilder};
use crate::Db; use crate::Db;
use super::constraint::{Constraint, ConstraintNode, PatternConstraint}; use super::constraint::{Constraint, ConstraintNode, PatternConstraint};
@ -232,6 +230,11 @@ impl<'db> SemanticIndexBuilder<'db> {
&self.use_def_maps[scope_id] &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 { fn current_ast_ids(&mut self) -> &mut AstIdsBuilder {
let scope_id = self.current_scope(); let scope_id = self.current_scope();
&mut self.ast_ids[scope_id] &mut self.ast_ids[scope_id]
@ -367,21 +370,11 @@ impl<'db> SemanticIndexBuilder<'db> {
id 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 /// Records a previously added visibility constraint by applying it to all live bindings
/// and declarations. /// and declarations.
fn record_visibility_constraint_id(&mut self, constraint: ScopedVisibilityConstraintId) { fn record_visibility_constraint_id(&mut self, constraint: ScopedVisibilityConstraintId) {
self.current_use_def_map_mut() 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. /// 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, &mut self,
constraint: ScopedVisibilityConstraintId, constraint: ScopedVisibilityConstraintId,
) -> ScopedVisibilityConstraintId { ) -> ScopedVisibilityConstraintId {
self.current_use_def_map_mut() let id = self
.record_visibility_constraint(VisibilityConstraint::VisibleIfNot(constraint)) .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. /// Records a visibility constraint by applying it to all live bindings and declarations.
@ -398,8 +394,11 @@ impl<'db> SemanticIndexBuilder<'db> {
&mut self, &mut self,
constraint: Constraint<'db>, constraint: Constraint<'db>,
) -> ScopedVisibilityConstraintId { ) -> ScopedVisibilityConstraintId {
self.current_use_def_map_mut() let id = self
.record_visibility_constraint(VisibilityConstraint::VisibleIf(constraint, 0)) .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 /// 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(); self.current_use_def_map_mut().mark_unreachable();
} }
/// Records a [`VisibilityConstraint::Ambiguous`] constraint. /// Records a visibility constraint that always evaluates to "ambiguous".
fn record_ambiguous_visibility(&mut self) -> ScopedVisibilityConstraintId { fn record_ambiguous_visibility(&mut self) {
self.current_use_def_map_mut() 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 /// 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, // 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 // since we need to model situations where the first evaluation of the condition
// returns True, but a later evaluation returns False. // returns True, but a later evaluation returns False.
let first_vis_constraint_id = let first_vis_constraint_id = self
self.add_visibility_constraint(VisibilityConstraint::VisibleIf(constraint, 0)); .current_visibility_constraints_mut()
let later_vis_constraint_id = .add_atom(constraint, 0);
self.add_visibility_constraint(VisibilityConstraint::VisibleIf(constraint, 1)); let later_vis_constraint_id = self
.current_visibility_constraints_mut()
.add_atom(constraint, 1);
// Save aside any break states from an outer loop // Save aside any break states from an outer loop
let saved_break_states = std::mem::take(&mut self.loop_break_states); 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::And => (constraint, self.add_constraint(constraint)),
ast::BoolOp::Or => self.add_negated_constraint(constraint), ast::BoolOp::Or => self.add_negated_constraint(constraint),
}; };
let visibility_constraint = self.add_visibility_constraint( let visibility_constraint = self
VisibilityConstraint::VisibleIf(constraint, 0), .current_visibility_constraints_mut()
); .add_atom(constraint, 0);
let after_expr = self.flow_snapshot(); let after_expr = self.flow_snapshot();

View file

@ -255,16 +255,18 @@
//! snapshot, and merging a snapshot into the current state. The logic using these methods lives in //! 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 //! [`SemanticIndexBuilder`](crate::semantic_index::builder::SemanticIndexBuilder), e.g. where it
//! visits a `StmtIf` node. //! visits a `StmtIf` node.
pub(crate) use self::symbol_state::ScopedConstraintId;
use self::symbol_state::{ use self::symbol_state::{
BindingIdWithConstraintsIterator, ConstraintIdIterator, DeclarationIdIterator, BindingIdWithConstraintsIterator, ConstraintIdIterator, DeclarationIdIterator,
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::ast_ids::ScopedUseId;
use crate::semantic_index::definition::Definition; use crate::semantic_index::definition::Definition;
use crate::semantic_index::symbol::ScopedSymbolId; use crate::semantic_index::symbol::ScopedSymbolId;
use crate::semantic_index::use_def::symbol_state::DeclarationIdWithConstraint; 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 ruff_index::IndexVec;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
@ -285,7 +287,7 @@ pub(crate) struct UseDefMap<'db> {
/// Array of [`Constraint`] in this scope. /// Array of [`Constraint`] in this scope.
all_constraints: AllConstraints<'db>, all_constraints: AllConstraints<'db>,
/// Array of [`VisibilityConstraint`]s in this scope. /// Array of visibility constraints in this scope.
visibility_constraints: VisibilityConstraints<'db>, visibility_constraints: VisibilityConstraints<'db>,
/// [`SymbolBindings`] reaching a [`ScopedUseId`]. /// [`SymbolBindings`] reaching a [`ScopedUseId`].
@ -487,8 +489,8 @@ pub(super) struct UseDefMapBuilder<'db> {
/// Append-only array of [`Constraint`]. /// Append-only array of [`Constraint`].
all_constraints: AllConstraints<'db>, all_constraints: AllConstraints<'db>,
/// Append-only array of [`VisibilityConstraint`]. /// Builder of visibility constraints.
visibility_constraints: VisibilityConstraints<'db>, pub(super) visibility_constraints: VisibilityConstraintsBuilder<'db>,
/// A constraint which describes the visibility of the unbound/undeclared state, i.e. /// 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 /// 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 { Self {
all_definitions: IndexVec::from_iter([None]), all_definitions: IndexVec::from_iter([None]),
all_constraints: IndexVec::new(), all_constraints: IndexVec::new(),
visibility_constraints: VisibilityConstraints::default(), visibility_constraints: VisibilityConstraintsBuilder::default(),
scope_start_visibility: ScopedVisibilityConstraintId::ALWAYS_TRUE, scope_start_visibility: ScopedVisibilityConstraintId::ALWAYS_TRUE,
bindings_by_use: IndexVec::new(), bindings_by_use: IndexVec::new(),
definitions_by_definition: FxHashMap::default(), definitions_by_definition: FxHashMap::default(),
@ -561,35 +563,18 @@ impl<'db> UseDefMapBuilder<'db> {
new_constraint_id new_constraint_id
} }
pub(super) fn add_visibility_constraint( pub(super) fn record_visibility_constraint(
&mut self,
constraint: VisibilityConstraint<'db>,
) -> ScopedVisibilityConstraintId {
self.visibility_constraints.add(constraint)
}
pub(super) fn record_visibility_constraint_id(
&mut self, &mut self,
constraint: ScopedVisibilityConstraintId, constraint: ScopedVisibilityConstraintId,
) { ) {
for state in &mut self.symbol_states { for state in &mut self.symbol_states {
state.record_visibility_constraint(&mut self.visibility_constraints, constraint); state.record_visibility_constraint(&mut self.visibility_constraints, constraint);
} }
self.scope_start_visibility = self self.scope_start_visibility = self
.visibility_constraints .visibility_constraints
.add_and_constraint(self.scope_start_visibility, constraint); .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 /// 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 /// *if* there have been no new declarations or bindings since then. Consider the
/// following example: /// following example:
@ -742,7 +727,7 @@ impl<'db> UseDefMapBuilder<'db> {
UseDefMap { UseDefMap {
all_definitions: self.all_definitions, all_definitions: self.all_definitions,
all_constraints: self.all_constraints, all_constraints: self.all_constraints,
visibility_constraints: self.visibility_constraints, visibility_constraints: self.visibility_constraints.build(),
bindings_by_use: self.bindings_by_use, bindings_by_use: self.bindings_by_use,
public_symbols: self.symbol_states, public_symbols: self.symbol_states,
definitions_by_definition: self.definitions_by_definition, definitions_by_definition: self.definitions_by_definition,

View file

@ -49,7 +49,8 @@ use ruff_index::newtype_index;
use smallvec::SmallVec; use smallvec::SmallVec;
use crate::semantic_index::use_def::bitset::{BitSet, BitSetIterator}; 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. /// A newtype-index for a definition in a particular scope.
#[newtype_index] #[newtype_index]
@ -99,18 +100,6 @@ type ConstraintsPerBinding = SmallVec<InlineConstraintArray>;
/// Iterate over all constraints for a single binding. /// Iterate over all constraints for a single binding.
type ConstraintsIterator<'a> = std::slice::Iter<'a, Constraints>; 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; const INLINE_VISIBILITY_CONSTRAINTS: usize = 4;
type InlineVisibilityConstraintsArray = type InlineVisibilityConstraintsArray =
[ScopedVisibilityConstraintId; INLINE_VISIBILITY_CONSTRAINTS]; [ScopedVisibilityConstraintId; INLINE_VISIBILITY_CONSTRAINTS];
@ -164,7 +153,7 @@ impl SymbolDeclarations {
/// Add given visibility constraint to all live declarations. /// Add given visibility constraint to all live declarations.
pub(super) fn record_visibility_constraint( pub(super) fn record_visibility_constraint(
&mut self, &mut self,
visibility_constraints: &mut VisibilityConstraints, visibility_constraints: &mut VisibilityConstraintsBuilder,
constraint: ScopedVisibilityConstraintId, constraint: ScopedVisibilityConstraintId,
) { ) {
for existing in &mut self.visibility_constraints { 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); let a = std::mem::take(self);
self.live_declarations = a.live_declarations.clone(); self.live_declarations = a.live_declarations.clone();
self.live_declarations.union(&b.live_declarations); self.live_declarations.union(&b.live_declarations);
@ -270,7 +259,7 @@ impl SymbolBindings {
/// Add given visibility constraint to all live bindings. /// Add given visibility constraint to all live bindings.
pub(super) fn record_visibility_constraint( pub(super) fn record_visibility_constraint(
&mut self, &mut self,
visibility_constraints: &mut VisibilityConstraints, visibility_constraints: &mut VisibilityConstraintsBuilder,
constraint: ScopedVisibilityConstraintId, constraint: ScopedVisibilityConstraintId,
) { ) {
for existing in &mut self.visibility_constraints { 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); let mut a = std::mem::take(self);
self.live_bindings = a.live_bindings.clone(); self.live_bindings = a.live_bindings.clone();
self.live_bindings.union(&b.live_bindings); self.live_bindings.union(&b.live_bindings);
@ -373,7 +362,7 @@ impl SymbolState {
/// Add given visibility constraint to all live bindings. /// Add given visibility constraint to all live bindings.
pub(super) fn record_visibility_constraint( pub(super) fn record_visibility_constraint(
&mut self, &mut self,
visibility_constraints: &mut VisibilityConstraints, visibility_constraints: &mut VisibilityConstraintsBuilder,
constraint: ScopedVisibilityConstraintId, constraint: ScopedVisibilityConstraintId,
) { ) {
self.bindings self.bindings
@ -401,7 +390,7 @@ impl SymbolState {
pub(super) fn merge( pub(super) fn merge(
&mut self, &mut self,
b: SymbolState, b: SymbolState,
visibility_constraints: &mut VisibilityConstraints, visibility_constraints: &mut VisibilityConstraintsBuilder,
) { ) {
self.bindings.merge(b.bindings, visibility_constraints); self.bindings.merge(b.bindings, visibility_constraints);
self.declarations self.declarations
@ -584,7 +573,7 @@ mod tests {
#[test] #[test]
fn merge() { 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 // merging the same definition with the same constraint keeps the constraint
let mut sym1a = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); let mut sym1a = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
@ -655,7 +644,7 @@ mod tests {
#[test] #[test]
fn record_declaration_merge() { fn record_declaration_merge() {
let mut visibility_constraints = VisibilityConstraints::default(); let mut visibility_constraints = VisibilityConstraintsBuilder::default();
let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
sym.record_declaration(ScopedDefinitionId::from_u32(1)); sym.record_declaration(ScopedDefinitionId::from_u32(1));
@ -669,7 +658,7 @@ mod tests {
#[test] #[test]
fn record_declaration_merge_partial_undeclared() { 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); let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
sym.record_declaration(ScopedDefinitionId::from_u32(1)); sym.record_declaration(ScopedDefinitionId::from_u32(1));

View file

@ -122,7 +122,7 @@
//! //!
//! ### Explicit ambiguity //! ### 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 //! 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: //! intentionally do not want to) analyze statically. `for` loops are one example:
//! ```py //! ```py
@ -150,9 +150,8 @@
//! //!
//! [Kleene]: <https://en.wikipedia.org/wiki/Three-valued_logic#Kleene_and_Priest_logics> //! [Kleene]: <https://en.wikipedia.org/wiki/Three-valued_logic#Kleene_and_Priest_logics>
use ruff_index::IndexVec; use ruff_index::{newtype_index, IndexVec};
use crate::semantic_index::ScopedVisibilityConstraintId;
use crate::semantic_index::{ use crate::semantic_index::{
ast_ids::HasScopedExpressionId, ast_ids::HasScopedExpressionId,
constraint::{Constraint, ConstraintNode, PatternConstraintKind}, 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 /// 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. /// property might be different at different points in the execution of the program.
#[derive(Clone, Debug, PartialEq, Eq)] #[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, AlwaysTrue,
AlwaysFalse,
Ambiguous, Ambiguous,
VisibleIf(Constraint<'db>, u8), VisibleIf(Constraint<'db>, u8),
VisibleIfNot(ScopedVisibilityConstraintId), VisibleIfNot(ScopedVisibilityConstraintId),
@ -193,25 +196,84 @@ pub(crate) enum VisibilityConstraint<'db> {
KleeneOr(ScopedVisibilityConstraintId, ScopedVisibilityConstraintId), 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)] #[derive(Debug, PartialEq, Eq)]
pub(crate) struct VisibilityConstraints<'db> { pub(crate) struct VisibilityConstraints<'db> {
constraints: IndexVec<ScopedVisibilityConstraintId, VisibilityConstraint<'db>>, constraints: IndexVec<ScopedVisibilityConstraintId, VisibilityConstraint<'db>>,
} }
impl Default for VisibilityConstraints<'_> { #[derive(Debug, PartialEq, Eq)]
pub(crate) struct VisibilityConstraintsBuilder<'db> {
constraints: IndexVec<ScopedVisibilityConstraintId, VisibilityConstraint<'db>>,
}
impl Default for VisibilityConstraintsBuilder<'_> {
fn default() -> Self { fn default() -> Self {
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> { impl<'db> VisibilityConstraintsBuilder<'db> {
pub(crate) fn add( 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, &mut self,
constraint: VisibilityConstraint<'db>, constraint: Constraint<'db>,
copy: u8,
) -> ScopedVisibilityConstraintId { ) -> ScopedVisibilityConstraintId {
self.constraints.push(constraint) self.add(VisibilityConstraintInner::VisibleIf(constraint, copy))
}
pub(crate) fn add_not_constraint(
&mut self,
a: ScopedVisibilityConstraintId,
) -> ScopedVisibilityConstraintId {
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( pub(crate) fn add_or_constraint(
@ -219,14 +281,23 @@ impl<'db> VisibilityConstraints<'db> {
a: ScopedVisibilityConstraintId, a: ScopedVisibilityConstraintId,
b: ScopedVisibilityConstraintId, b: ScopedVisibilityConstraintId,
) -> 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]) { match (&self.constraints[a], &self.constraints[b]) {
(_, VisibilityConstraint::VisibleIfNot(id)) if a == *id => { (_, VisibilityConstraint(VisibilityConstraintInner::VisibleIfNot(id))) if a == *id => {
ScopedVisibilityConstraintId::ALWAYS_TRUE ScopedVisibilityConstraintId::ALWAYS_TRUE
} }
(VisibilityConstraint::VisibleIfNot(id), _) if *id == b => { (VisibilityConstraint(VisibilityConstraintInner::VisibleIfNot(id)), _) if *id == b => {
ScopedVisibilityConstraintId::ALWAYS_TRUE 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, a: ScopedVisibilityConstraintId,
b: ScopedVisibilityConstraintId, b: ScopedVisibilityConstraintId,
) -> 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; return b;
} else if b == ScopedVisibilityConstraintId::ALWAYS_TRUE { } else if b == ScopedVisibilityConstraintId::ALWAYS_TRUE {
return a; return a;
} }
match (&self.constraints[a], &self.constraints[b]) { match (&self.constraints[a], &self.constraints[b]) {
(_, VisibilityConstraint::VisibleIfNot(id)) if a == *id => self.add( (_, VisibilityConstraint(VisibilityConstraintInner::VisibleIfNot(id))) if a == *id => {
VisibilityConstraint::VisibleIfNot(ScopedVisibilityConstraintId::ALWAYS_TRUE), ScopedVisibilityConstraintId::ALWAYS_FALSE
), }
(VisibilityConstraint::VisibleIfNot(id), _) if *id == b => self.add( (VisibilityConstraint(VisibilityConstraintInner::VisibleIfNot(id)), _) if *id == b => {
VisibilityConstraint::VisibleIfNot(ScopedVisibilityConstraintId::ALWAYS_TRUE), ScopedVisibilityConstraintId::ALWAYS_FALSE
), }
_ => self.add(VisibilityConstraint::KleeneAnd(a, b)), _ => self.add(VisibilityConstraintInner::KleeneAnd(a, b)),
} }
} }
}
impl<'db> VisibilityConstraints<'db> {
/// Analyze the statically known visibility for a given visibility constraint. /// Analyze the statically known visibility for a given visibility constraint.
pub(crate) fn evaluate(&self, db: &'db dyn Db, id: ScopedVisibilityConstraintId) -> Truthiness { pub(crate) fn evaluate(&self, db: &'db dyn Db, id: ScopedVisibilityConstraintId) -> Truthiness {
self.evaluate_impl(db, id, MAX_RECURSION_DEPTH) self.evaluate_impl(db, id, MAX_RECURSION_DEPTH)
@ -266,15 +343,18 @@ impl<'db> VisibilityConstraints<'db> {
return Truthiness::Ambiguous; return Truthiness::Ambiguous;
} }
let visibility_constraint = &self.constraints[id]; let VisibilityConstraint(visibility_constraint) = &self.constraints[id];
match visibility_constraint { match visibility_constraint {
VisibilityConstraint::AlwaysTrue => Truthiness::AlwaysTrue, VisibilityConstraintInner::AlwaysTrue => Truthiness::AlwaysTrue,
VisibilityConstraint::Ambiguous => Truthiness::Ambiguous, VisibilityConstraintInner::AlwaysFalse => Truthiness::AlwaysFalse,
VisibilityConstraint::VisibleIf(constraint, _) => Self::analyze_single(db, constraint), VisibilityConstraintInner::Ambiguous => Truthiness::Ambiguous,
VisibilityConstraint::VisibleIfNot(negated) => { VisibilityConstraintInner::VisibleIf(constraint, _) => {
Self::analyze_single(db, constraint)
}
VisibilityConstraintInner::VisibleIfNot(negated) => {
self.evaluate_impl(db, *negated, max_depth - 1).negate() 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); let lhs = self.evaluate_impl(db, *lhs, max_depth - 1);
if lhs == Truthiness::AlwaysFalse { if lhs == Truthiness::AlwaysFalse {
@ -291,7 +371,7 @@ impl<'db> VisibilityConstraints<'db> {
Truthiness::Ambiguous 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); let lhs = self.evaluate_impl(db, *lhs_id, max_depth - 1);
if lhs == Truthiness::AlwaysTrue { if lhs == Truthiness::AlwaysTrue {