use std::fmt::{Debug, Display, Formatter};
use std::iter::FusedIterator;
use ruff_macros::CacheKey;
use crate::registry::Rule;
const RULESET_SIZE: usize = 16;
#[derive(Clone, Default, CacheKey, PartialEq, Eq)]
pub struct RuleSet([u64; RULESET_SIZE]);
impl RuleSet {
const EMPTY: [u64; RULESET_SIZE] = [0; RULESET_SIZE];
#[expect(clippy::cast_possible_truncation)]
const SLICE_BITS: u16 = u64::BITS as u16;
pub const fn empty() -> Self {
Self(Self::EMPTY)
}
#[inline]
pub const fn from_rule(rule: Rule) -> Self {
let rule = rule as u16;
let index = (rule / Self::SLICE_BITS) as usize;
debug_assert!(
index < Self::EMPTY.len(),
"Rule index out of bounds. Increase the size of the bitset array."
);
let shift = rule % Self::SLICE_BITS;
let mask = 1 << shift;
let mut bits = Self::EMPTY;
bits[index] = mask;
Self(bits)
}
#[inline]
pub const fn from_rules(rules: &[Rule]) -> Self {
let mut set = RuleSet::empty();
let mut i = 0;
while i < rules.len() {
set = set.union(&RuleSet::from_rule(rules[i]));
i += 1;
}
set
}
#[must_use]
pub const fn union(mut self, other: &Self) -> Self {
let mut i = 0;
while i < self.0.len() {
self.0[i] |= other.0[i];
i += 1;
}
self
}
#[must_use]
pub const fn subtract(mut self, other: &Self) -> Self {
let mut i = 0;
while i < self.0.len() {
self.0[i] &= !other.0[i];
i += 1;
}
self
}
pub const fn intersects(&self, other: &Self) -> bool {
let mut i = 0;
while i < self.0.len() {
if self.0[i] & other.0[i] != 0 {
return true;
}
i += 1;
}
false
}
pub const fn is_empty(&self) -> bool {
self.len() == 0
}
pub const fn len(&self) -> usize {
let mut len: u32 = 0;
let mut i = 0;
while i < self.0.len() {
len += self.0[i].count_ones();
i += 1;
}
len as usize
}
pub fn insert(&mut self, rule: Rule) {
let set = std::mem::take(self);
*self = set.union(&RuleSet::from_rule(rule));
}
#[inline]
pub fn set(&mut self, rule: Rule, enabled: bool) {
if enabled {
self.insert(rule);
} else {
self.remove(rule);
}
}
pub fn remove(&mut self, rule: Rule) {
let set = std::mem::take(self);
*self = set.subtract(&RuleSet::from_rule(rule));
}
#[inline]
pub const fn contains(&self, rule: Rule) -> bool {
let rule = rule as u16;
let index = rule as usize / Self::SLICE_BITS as usize;
let shift = rule % Self::SLICE_BITS;
let mask = 1 << shift;
self.0[index] & mask != 0
}
#[inline]
pub(crate) const fn any(&self, rules: &[Rule]) -> bool {
let mut any = false;
let mut i = 0;
while i < rules.len() {
any |= self.contains(rules[i]);
i += 1;
}
any
}
pub fn iter(&self) -> RuleSetIterator {
RuleSetIterator {
set: self.clone(),
index: 0,
}
}
}
impl Debug for RuleSet {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_set().entries(self.iter()).finish()
}
}
impl Display for RuleSet {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if self.is_empty() {
write!(f, "[]")?;
} else {
writeln!(f, "[")?;
for rule in self {
let code = rule.noqa_code();
writeln!(f, "\t{name} ({code}),", name = rule.name())?;
}
write!(f, "]")?;
}
Ok(())
}
}
impl FromIterator<Rule> for RuleSet {
fn from_iter<T: IntoIterator<Item = Rule>>(iter: T) -> Self {
let mut set = RuleSet::empty();
for rule in iter {
set.insert(rule);
}
set
}
}
impl Extend<Rule> for RuleSet {
fn extend<T: IntoIterator<Item = Rule>>(&mut self, iter: T) {
let set = std::mem::take(self);
*self = set.union(&RuleSet::from_iter(iter));
}
}
impl IntoIterator for RuleSet {
type IntoIter = RuleSetIterator;
type Item = Rule;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl IntoIterator for &RuleSet {
type IntoIter = RuleSetIterator;
type Item = Rule;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
pub struct RuleSetIterator {
set: RuleSet,
index: u16,
}
impl Iterator for RuleSetIterator {
type Item = Rule;
fn next(&mut self) -> Option<Self::Item> {
loop {
let slice = self.set.0.get_mut(self.index as usize)?;
#[expect(clippy::cast_possible_truncation)]
let bit = slice.trailing_zeros() as u16;
if bit < RuleSet::SLICE_BITS {
*slice ^= 1 << bit;
let rule_value = self.index * RuleSet::SLICE_BITS + bit;
#[expect(unsafe_code)]
return Some(unsafe { std::mem::transmute::<u16, Rule>(rule_value) });
}
self.index += 1;
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let len = self.set.len();
(len, Some(len))
}
}
impl ExactSizeIterator for RuleSetIterator {}
impl FusedIterator for RuleSetIterator {}
#[cfg(test)]
mod tests {
use strum::IntoEnumIterator;
use crate::registry::{Rule, RuleSet};
#[test]
fn test_all_rules() {
for rule in Rule::iter() {
let set = RuleSet::from_rule(rule);
assert!(set.contains(rule));
}
let all_rules_set: RuleSet = Rule::iter().collect();
let all_rules: Vec<_> = all_rules_set.iter().collect();
let expected_rules: Vec<_> = Rule::iter().collect();
assert_eq!(all_rules, expected_rules);
}
#[test]
fn remove_not_existing_rule_from_set() {
let mut set = RuleSet::default();
set.remove(Rule::AmbiguousFunctionName);
assert!(!set.contains(Rule::AmbiguousFunctionName));
assert!(set.is_empty());
assert_eq!(set.into_iter().collect::<Vec<_>>(), vec![]);
}
}