diff --git a/src/builtin/functions/selector.rs b/src/builtin/functions/selector.rs index 28c2f64..ecccfce 100644 --- a/src/builtin/functions/selector.rs +++ b/src/builtin/functions/selector.rs @@ -116,10 +116,7 @@ pub(crate) fn selector_append(args: CallArgs, parser: &mut Parser<'_>) -> SassRe } }]; components.extend(complex.components.into_iter().skip(1)); - Ok(ComplexSelector { - components, - line_break: false, - }) + Ok(ComplexSelector::new(components, false)) } else { Err((format!("Can't append {} to {}.", complex, parent), span).into()) } diff --git a/src/selector/complex.rs b/src/selector/complex.rs index a2160ec..dd8be40 100644 --- a/src/selector/complex.rs +++ b/src/selector/complex.rs @@ -1,12 +1,37 @@ use std::{ + collections::HashSet, fmt::{self, Display, Write}, hash::{Hash, Hasher}, + sync::atomic::{AtomicU32, Ordering as AtomicOrdering}, }; use crate::error::SassResult; use super::{CompoundSelector, Pseudo, SelectorList, SimpleSelector, Specificity}; +pub(crate) static COMPLEX_SELECTOR_UNIQUE_ID: AtomicU32 = AtomicU32::new(0); + +#[derive(Clone, Debug)] +pub(crate) struct ComplexSelectorHashSet(HashSet); + +impl ComplexSelectorHashSet { + pub fn new() -> Self { + Self(HashSet::new()) + } + + pub fn insert(&mut self, complex: &ComplexSelector) -> bool { + self.0.insert(complex.unique_id) + } + + pub fn contains(&self, complex: &ComplexSelector) -> bool { + self.0.contains(&complex.unique_id) + } + + pub fn extend<'a>(&mut self, complexes: impl Iterator) { + self.0.extend(complexes.map(|complex| complex.unique_id)); + } +} + /// A complex selector. /// /// A complex selector is composed of `CompoundSelector`s separated by @@ -27,6 +52,11 @@ pub(crate) struct ComplexSelector { /// Whether a line break should be emitted *before* this selector. pub line_break: bool, + + /// A unique identifier for this complex selector. Used to perform a pointer + /// equality check, like would be done for objects in a language like JavaScript + /// or dart + unique_id: u32, } impl PartialEq for ComplexSelector { @@ -68,6 +98,14 @@ fn omit_spaces_around(component: &ComplexSelectorComponent) -> bool { } impl ComplexSelector { + pub fn new(components: Vec, line_break: bool) -> Self { + Self { + components, + line_break, + unique_id: COMPLEX_SELECTOR_UNIQUE_ID.fetch_add(1, AtomicOrdering::Relaxed), + } + } + pub fn max_specificity(&self) -> i32 { self.specificity().min } diff --git a/src/selector/compound.rs b/src/selector/compound.rs index 6ba1e0d..8b4ae07 100644 --- a/src/selector/compound.rs +++ b/src/selector/compound.rs @@ -155,12 +155,12 @@ impl CompoundSelector { return Ok(Some(parent.components)); } } else { - return Ok(Some(vec![ComplexSelector { - components: vec![ComplexSelectorComponent::Compound(CompoundSelector { + return Ok(Some(vec![ComplexSelector::new( + vec![ComplexSelectorComponent::Compound(CompoundSelector { components: resolved_members, })], - line_break: false, - }])); + false, + )])); } let span = parent.span; @@ -201,10 +201,7 @@ impl CompoundSelector { let mut components = complex.components; components.push(ComplexSelectorComponent::Compound(last)); - Ok(ComplexSelector { - components, - line_break: complex.line_break, - }) + Ok(ComplexSelector::new(components, complex.line_break)) }) .collect::>>()?, )) diff --git a/src/selector/extend/functions.rs b/src/selector/extend/functions.rs index 1854767..25d3eb0 100644 --- a/src/selector/extend/functions.rs +++ b/src/selector/extend/functions.rs @@ -692,14 +692,8 @@ fn complex_is_parent_superselector( complex_one.push(ComplexSelectorComponent::Compound(base.clone())); complex_two.push(ComplexSelectorComponent::Compound(base)); - ComplexSelector { - components: complex_one, - line_break: false, - } - .is_super_selector(&ComplexSelector { - components: complex_two, - line_break: false, - }) + ComplexSelector::new(complex_one, false) + .is_super_selector(&ComplexSelector::new(complex_two, false)) } /// Returns a list of all possible paths through the given lists. diff --git a/src/selector/extend/mod.rs b/src/selector/extend/mod.rs index 0b6f5af..4eeaffe 100644 --- a/src/selector/extend/mod.rs +++ b/src/selector/extend/mod.rs @@ -10,8 +10,8 @@ use indexmap::IndexMap; use crate::error::SassResult; use super::{ - ComplexSelector, ComplexSelectorComponent, CompoundSelector, Pseudo, SelectorList, - SimpleSelector, + ComplexSelector, ComplexSelectorComponent, ComplexSelectorHashSet, CompoundSelector, Pseudo, + SelectorList, SimpleSelector, }; pub(crate) use extended_selector::ExtendedSelector; @@ -99,7 +99,7 @@ pub(crate) struct Extender { /// exist to satisfy the [first law of extend][]. /// /// [first law of extend]: https://github.com/sass/sass/issues/324#issuecomment-4607184 - originals: HashSet, + originals: ComplexSelectorHashSet, /// The mode that controls this extender's behavior. mode: ExtendMode, @@ -129,7 +129,7 @@ impl Extender { extensions_by_extender: HashMap::new(), media_contexts: HashMap::new(), source_specificity: HashMap::new(), - originals: HashSet::new(), + originals: ComplexSelectorHashSet::new(), mode: ExtendMode::Normal, span, } @@ -188,9 +188,7 @@ impl Extender { let mut extender = Extender::with_mode(mode, span); if !selector.is_invisible() { - extender - .originals - .extend(selector.components.iter().cloned()); + extender.originals.extend(selector.components.iter()); } Ok(extender.extend_list(selector, Some(&extensions), &None)) @@ -288,10 +286,7 @@ impl Extender { .into_iter() .take(i) .map(|component| { - vec![ComplexSelector { - components: vec![component], - line_break: complex.line_break, - }] + vec![ComplexSelector::new(vec![component], complex.line_break)] }) .collect(), ); @@ -302,19 +297,16 @@ impl Extender { } } else { match extended_not_expanded.as_mut() { - Some(v) => v.push(vec![ComplexSelector { - components: vec![ComplexSelectorComponent::Compound(component.clone())], - line_break: false, - }]), + Some(v) => v.push(vec![ComplexSelector::new( + vec![ComplexSelectorComponent::Compound(component.clone())], + false, + )]), None => {} } } } else if component.is_combinator() { match extended_not_expanded.as_mut() { - Some(v) => v.push(vec![ComplexSelector { - components: vec![component.clone()], - line_break: false, - }]), + Some(v) => v.push(vec![ComplexSelector::new(vec![component.clone()], false)]), None => {} } } @@ -336,17 +328,17 @@ impl Extender { ) .into_iter() .map(|components| { - let output_complex = ComplexSelector { + let output_complex = ComplexSelector::new( components, - line_break: complex_has_line_break + complex_has_line_break || path.iter().any(|input_complex| input_complex.line_break), - }; + ); // Make sure that copies of `complex` retain their status as "original" // selectors. This includes selectors that are modified because a :not() // was extended into. - if first && self.originals.contains(&complex.clone()) { - self.originals.insert(output_complex.clone()); + if first && self.originals.contains(&complex) { + self.originals.insert(&output_complex); } first = false; @@ -517,10 +509,7 @@ impl Extender { Some( complexes .into_iter() - .map(|components| ComplexSelector { - components, - line_break, - }) + .map(|components| ComplexSelector::new(components, line_break)) .collect::>(), ) }); @@ -742,12 +731,12 @@ impl Extender { fn extension_for_simple(&self, simple: SimpleSelector) -> Extension { let specificity = Some(*self.source_specificity.get(&simple).unwrap_or(&0_i32)); Extension::one_off( - ComplexSelector { - components: vec![ComplexSelectorComponent::Compound(CompoundSelector { + ComplexSelector::new( + vec![ComplexSelectorComponent::Compound(CompoundSelector { components: vec![simple], })], - line_break: false, - }, + false, + ), specificity, true, self.span, @@ -762,10 +751,7 @@ impl Extender { }; let specificity = Some(self.source_specificity_for(&compound)); Extension::one_off( - ComplexSelector { - components: vec![ComplexSelectorComponent::Compound(compound)], - line_break: false, - }, + ComplexSelector::new(vec![ComplexSelectorComponent::Compound(compound)], false), specificity, true, self.span, @@ -883,7 +869,7 @@ impl Extender { ) -> ExtendedSelector { if !selector.is_invisible() { for complex in selector.components.clone() { - self.originals.insert(complex); + self.originals.insert(&complex); } } diff --git a/src/selector/list.rs b/src/selector/list.rs index 813c4f3..b00aa55 100644 --- a/src/selector/list.rs +++ b/src/selector/list.rs @@ -128,10 +128,7 @@ impl SelectorList { unify_complex(vec![c1.components.clone(), c2.components]); if let Some(u) = unified { u.into_iter() - .map(|c| ComplexSelector { - components: c, - line_break: false, - }) + .map(|c| ComplexSelector::new(c, false)) .collect() } else { Vec::new() @@ -193,10 +190,10 @@ impl SelectorList { .map(move |parent_complex| { let mut components = parent_complex.components; components.append(&mut complex.components.clone()); - ComplexSelector { + ComplexSelector::new( components, - line_break: complex.line_break || parent_complex.line_break, - } + complex.line_break || parent_complex.line_break, + ) }) .collect()); } @@ -245,10 +242,7 @@ impl SelectorList { .into_iter() .map(|new_complex| { i += 1; - ComplexSelector { - components: new_complex, - line_break: line_breaks[i - 1], - } + ComplexSelector::new(new_complex, line_breaks[i - 1]) }) .collect()) }) diff --git a/src/selector/parse.rs b/src/selector/parse.rs index 3f7e472..ee524c4 100644 --- a/src/selector/parse.rs +++ b/src/selector/parse.rs @@ -182,10 +182,7 @@ impl<'a, 'b> SelectorParser<'a, 'b> { return Err(("expected selector.", self.span).into()); } - Ok(ComplexSelector { - components, - line_break, - }) + Ok(ComplexSelector::new(components, line_break)) } fn parse_compound_selector(&mut self) -> SassResult { diff --git a/src/selector/simple.rs b/src/selector/simple.rs index 08e2be6..98b3bac 100644 --- a/src/selector/simple.rs +++ b/src/selector/simple.rs @@ -502,10 +502,7 @@ impl Pseudo { .any(move |complex1| { let mut components = parents.clone().unwrap_or_default(); components.push(ComplexSelectorComponent::Compound(compound.clone())); - complex1.is_super_selector(&ComplexSelector { - components, - line_break: false, - }) + complex1.is_super_selector(&ComplexSelector::new(components, false)) }) } "has" | "host" | "host-context" => { diff --git a/tests/extend.rs b/tests/extend.rs index 37eea73..42197ca 100644 --- a/tests/extend.rs +++ b/tests/extend.rs @@ -1906,6 +1906,22 @@ test!( }", "a, b {\n color: a;\n}\n" ); +test!( + complex_selector_with_combinator_removed_by_complex_selector_without_combinator, + "c b { + @extend %d; + } + + c > b { + @extend %d; + } + + %d { + color: red; + }", + "c b {\n color: red;\n}\n" +); + error!( extend_optional_keyword_not_complete, "a {