From ace6628c71c88595c00ead65ee1f60686b4d42a9 Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Thu, 29 Nov 2018 13:13:58 +0000 Subject: [PATCH] Re-implement selector unification This new implementation is similar to the one in dart-sass. Also fixes a few bugs in hash calculation (`==` objects having different hashes) and comparison (`a < b` and `b < a` being true at the same time; this should also fix #2776). The following tests are fixed by this change: ``` /spec/selector-functions/unify/universal_simple /spec/extend-tests/237_extend_with_universal_selector_different_namespace /spec/extend-tests/040_test_universal_unification_with_namespaced_element_target /spec/extend-tests/053_test_element_unification_with_namespaced_universal_target /spec/extend-tests/236_extend_with_universal_selector_empty_namespace /spec/extend-tests/096_test_long_extender_runs_unification /spec/extend-tests/060_test_element_unification_with_namespaceless_element_target /spec/extend-tests/051_test_element_unification_with_namespaceless_universal_target /spec/extend-tests/038_test_universal_unification_with_namespaceless_element_target /spec/extend-tests/029_test_universal_unification_with_namespaceless_universal_target /spec/extend-tests/031_test_universal_unification_with_namespaced_universal_target /spec/extend-tests/062_test_element_unification_with_namespaced_element_target ``` sass-spec output_styles update: https://github.com/sass/sass-spec/pull/1319/files --- include/sass/base.h | 1 + src/ast.hpp | 6 ++ src/ast_fwd_decl.hpp | 1 - src/ast_sel_cmp.cpp | 32 ++---- src/ast_sel_unify.cpp | 232 +++++++++++++++++++++++++----------------- src/ast_selectors.cpp | 110 ++++++++------------ src/ast_selectors.hpp | 7 +- src/extend.cpp | 3 +- src/parser.cpp | 2 +- src/util.hpp | 9 ++ 10 files changed, 210 insertions(+), 193 deletions(-) diff --git a/include/sass/base.h b/include/sass/base.h index 88dd8d303..eee8327be 100644 --- a/include/sass/base.h +++ b/include/sass/base.h @@ -1,6 +1,7 @@ #ifndef SASS_BASE_H #define SASS_BASE_H +// #define DEBUG // #define DEBUG_SHARED_PTR #ifdef _MSC_VER diff --git a/src/ast.hpp b/src/ast.hpp index abdb34a95..30282e842 100644 --- a/src/ast.hpp +++ b/src/ast.hpp @@ -319,6 +319,12 @@ namespace Sass { return hash_; } + template + typename std::vector::iterator insert(P position, const V& val) { + reset_hash(); + return elements_.insert(position, val); + } + typename std::vector::iterator end() { return elements_.end(); } typename std::vector::iterator begin() { return elements_.begin(); } typename std::vector::const_iterator end() const { return elements_.end(); } diff --git a/src/ast_fwd_decl.hpp b/src/ast_fwd_decl.hpp index d92e8d7d9..d68bedf5a 100644 --- a/src/ast_fwd_decl.hpp +++ b/src/ast_fwd_decl.hpp @@ -431,7 +431,6 @@ namespace Sass { typedef std::pair SubSetMapResult; typedef std::vector SubSetMapResults; - #define OrderSelectors OrderFunction typedef std::set SelectorSet; typedef std::deque ComplexSelectorDeque; diff --git a/src/ast_sel_cmp.cpp b/src/ast_sel_cmp.cpp index 72279da69..8be5d7538 100644 --- a/src/ast_sel_cmp.cpp +++ b/src/ast_sel_cmp.cpp @@ -467,10 +467,7 @@ namespace Sass { bool Simple_Selector::operator== (const Selector_List& rhs) const { - size_t len = rhs.length(); - if (len > 1) return false; - if (len == 0) return empty(); - return *this == *rhs.at(0); + return rhs.length() == 1 && *this == *rhs.at(0); } bool Simple_Selector::operator< (const Selector_List& rhs) const @@ -483,9 +480,9 @@ namespace Sass { bool Simple_Selector::operator== (const Complex_Selector& rhs) const { - if (rhs.tail()) return false; - if (!rhs.head()) return empty(); - return *this == *rhs.head(); + return !rhs.tail() && rhs.head() && + rhs.combinator() == Complex_Selector::ANCESTOR_OF && + *this == *rhs.head(); } bool Simple_Selector::operator< (const Complex_Selector& rhs) const @@ -497,10 +494,7 @@ namespace Sass { bool Simple_Selector::operator== (const Compound_Selector& rhs) const { - size_t len = rhs.length(); - if (len > 1) return false; - if (len == 0) return empty(); - return *this == *rhs.at(0); + return rhs.length() == 1 && *this == *rhs.at(0); } bool Simple_Selector::operator< (const Compound_Selector& rhs) const @@ -832,15 +826,11 @@ namespace Sass { bool Type_Selector::operator< (const Type_Selector& rhs) const { - if (is_ns_eq(rhs)) - { - if (rhs.has_ns_ && has_ns_) - return name() < rhs.name(); - if (!rhs.has_ns_ && !has_ns_) - return name() < rhs.name(); - return true; - } - return ns() < rhs.ns(); + return has_ns_ == rhs.has_ns_ + ? (ns_ == rhs.ns_ + ? name_ < rhs.name_ + : ns_ < rhs.ns_) + : has_ns_ < rhs.has_ns_; } bool Class_Selector::operator< (const Class_Selector& rhs) const @@ -912,4 +902,4 @@ namespace Sass { /*#########################################################################*/ /*#########################################################################*/ -} \ No newline at end of file +} diff --git a/src/ast_sel_unify.cpp b/src/ast_sel_unify.cpp index f8780eb5a..41c260e19 100644 --- a/src/ast_sel_unify.cpp +++ b/src/ast_sel_unify.cpp @@ -17,105 +17,136 @@ #include "ast_selectors.hpp" +// #define DEBUG_UNIFY + namespace Sass { Compound_Selector_Ptr Compound_Selector::unify_with(Compound_Selector_Ptr rhs) { + #ifdef DEBUG_UNIFY + const std::string debug_call = "unify(Compound[" + this->to_string() + "], Compound[" + rhs->to_string() + "])"; + std::cerr << debug_call << std::endl; + #endif + if (empty()) return rhs; Compound_Selector_Obj unified = SASS_MEMORY_COPY(rhs); for (const Simple_Selector_Obj& sel : elements()) { - if (unified.isNull()) break; unified = sel->unify_with(unified); + if (unified.isNull()) break; } + + #ifdef DEBUG_UNIFY + std::cerr << "> " << debug_call << " = Compound[" << unified->to_string() << "]" << std::endl; + #endif return unified.detach(); } Compound_Selector_Ptr Simple_Selector::unify_with(Compound_Selector_Ptr rhs) { + #ifdef DEBUG_UNIFY + const std::string debug_call = "unify(Simple[" + this->to_string() + "], Compound[" + rhs->to_string() + "])"; + std::cerr << debug_call << std::endl; + #endif + + if (rhs->length() == 1) { + if (rhs->at(0)->is_universal()) { + Compound_Selector_Ptr this_compound = SASS_MEMORY_NEW(Compound_Selector, pstate(), 1); + this_compound->append(SASS_MEMORY_COPY(this)); + Compound_Selector_Ptr unified = rhs->at(0)->unify_with(this_compound); + if (unified == nullptr || unified != this_compound) delete this_compound; + + #ifdef DEBUG_UNIFY + std::cerr << "> " << debug_call << " = " << "Compound[" << unified->to_string() << "]" << std::endl; + #endif + return unified; + } + } for (const Simple_Selector_Obj& sel : rhs->elements()) { - if (*this == *sel) return rhs; + if (*this == *sel) { + #ifdef DEBUG_UNIFY + std::cerr << "> " << debug_call << " = " << "Compound[" << rhs->to_string() << "]" << std::endl; + #endif + return rhs; + } } const int lhs_order = this->unification_order(); size_t i = rhs->length(); while (i > 0 && lhs_order < rhs->at(i - 1)->unification_order()) --i; - rhs->elements().insert(rhs->elements().begin() + i, this); + rhs->insert(rhs->begin() + i, this); + #ifdef DEBUG_UNIFY + std::cerr << "> " << debug_call << " = " << "Compound[" << rhs->to_string() << "]" << std::endl; + #endif return rhs; } Simple_Selector_Ptr Type_Selector::unify_with(Simple_Selector_Ptr rhs) { - // check if ns can be extended - // true for no ns or universal - if (has_universal_ns()) - { - // but dont extend with universal - // true for valid ns and universal - if (!rhs->is_universal_ns()) - { - // overwrite the name if star is given as name - if (this->name() == "*") { this->name(rhs->name()); } - // now overwrite the namespace name and flag - this->ns(rhs->ns()); this->has_ns(rhs->has_ns()); - // return copy - return this; + #ifdef DEBUG_UNIFY + const std::string debug_call = "unify(Type[" + this->to_string() + "], Simple[" + rhs->to_string() + "])"; + std::cerr << debug_call << std::endl; + #endif + + bool rhs_ns = false; + if (!(is_ns_eq(*rhs) || rhs->is_universal_ns())) { + if (!is_universal_ns()) { + #ifdef DEBUG_UNIFY + std::cerr << "> " << debug_call << " = nullptr" << std::endl; + #endif + return nullptr; + } + rhs_ns = true; + } + bool rhs_name = false; + if (!(name_ == rhs->name() || rhs->is_universal())) { + if (!(is_universal())) { + #ifdef DEBUG_UNIFY + std::cerr << "> " << debug_call << " = nullptr" << std::endl; + #endif + return nullptr; } + rhs_name = true; } - // namespace may changed, check the name now - // overwrite star (but not with another star) - if (name() == "*" && rhs->name() != "*") - { - // simply set the new name - this->name(rhs->name()); - // return copy - return this; + if (rhs_ns) { + ns(rhs->ns()); + has_ns(rhs->has_ns()); } - // return original + if (rhs_name) name(rhs->name()); + #ifdef DEBUG_UNIFY + std::cerr << "> " << debug_call << " = Simple[" << this->to_string() << "]" << std::endl; + #endif return this; } Compound_Selector_Ptr Type_Selector::unify_with(Compound_Selector_Ptr rhs) { - // TODO: handle namespaces + #ifdef DEBUG_UNIFY + const std::string debug_call = "unify(Type[" + this->to_string() + "], Compound[" + rhs->to_string() + "])"; + std::cerr << debug_call << std::endl; + #endif - // if the rhs is empty, just return a copy of this - if (rhs->length() == 0) { + if (rhs->empty()) { rhs->append(this); + #ifdef DEBUG_UNIFY + std::cerr << "> " << debug_call << " = Compound[" << rhs->to_string() << "]" << std::endl; + #endif return rhs; } - - Simple_Selector_Ptr rhs_0 = rhs->at(0); - // otherwise, this is a tag name - if (name() == "*") - { - if (typeid(*rhs_0) == typeid(Type_Selector)) - { - // if rhs is universal, just return this tagname + rhs's qualifiers - Type_Selector_Ptr ts = Cast(rhs_0); - rhs->at(0) = this->unify_with(ts); - return rhs; + Type_Selector_Ptr rhs_0 = Cast(rhs->at(0)); + if (rhs_0 != nullptr) { + Simple_Selector_Ptr unified = unify_with(rhs_0); + if (unified == nullptr) { + #ifdef DEBUG_UNIFY + std::cerr << "> " << debug_call << " = nullptr" << std::endl; + #endif + return nullptr; } - else if (Cast(rhs_0) || Cast(rhs_0)) { - // qualifier is `.class`, so we can prefix with `ns|*.class` - if (has_ns() && !rhs_0->has_ns()) { - if (ns() != "*") rhs->elements().insert(rhs->begin(), this); - } - return rhs; - } - - return rhs; - } - - if (typeid(*rhs_0) == typeid(Type_Selector)) - { - // if rhs is universal, just return this tagname + rhs's qualifiers - if (rhs_0->name() != "*" && rhs_0->ns() != "*" && rhs_0->name() != name()) return 0; - // otherwise create new compound and unify first simple selector - rhs->at(0) = this->unify_with(rhs_0); - return rhs; - + rhs->elements()[0] = unified; + } else if (!is_universal() || (has_ns_ && ns_ != "*")) { + rhs->insert(rhs->begin(), this); } - // else it's a tag name and a bunch of qualifiers -- just append them - if (name() != "*") rhs->elements().insert(rhs->begin(), this); + #ifdef DEBUG_UNIFY + std::cerr << "> " << debug_call << " = Compound[" << rhs->to_string() << "]" << std::endl; + #endif return rhs; } @@ -148,12 +179,16 @@ namespace Sass { return Simple_Selector::unify_with(rhs); } - Selector_List_Ptr Complex_Selector::unify_with(Complex_Selector_Ptr other) + Selector_List_Ptr Complex_Selector::unify_with(Complex_Selector_Ptr rhs) { + #ifdef DEBUG_UNIFY + const std::string debug_call = "unify(Complex[" + this->to_string() + "], Complex[" + rhs->to_string() + "])"; + std::cerr << debug_call << std::endl; + #endif // get last tails (on the right side) - Complex_Selector_Obj l_last = this->mutable_last(); - Complex_Selector_Obj r_last = other->mutable_last(); + Complex_Selector_Ptr l_last = this->mutable_last(); + Complex_Selector_Ptr r_last = rhs->mutable_last(); // check valid pointers (assertion) SASS_ASSERT(l_last, "lhs is null"); @@ -162,12 +197,12 @@ namespace Sass { // Not sure about this check, but closest way I could check // was to see if this is a ruby 'SimpleSequence' equivalent. // It seems to do the job correctly as some specs react to this - if (l_last->combinator() != Combinator::ANCESTOR_OF) return 0; - if (r_last->combinator() != Combinator::ANCESTOR_OF ) return 0; + if (l_last->combinator() != Combinator::ANCESTOR_OF) return nullptr; + if (r_last->combinator() != Combinator::ANCESTOR_OF) return nullptr; // get the headers for the last tails - Compound_Selector_Obj l_last_head = l_last->head(); - Compound_Selector_Obj r_last_head = r_last->head(); + Compound_Selector_Ptr l_last_head = l_last->head(); + Compound_Selector_Ptr r_last_head = r_last->head(); // check valid head pointers (assertion) SASS_ASSERT(l_last_head, "lhs head is null"); @@ -177,51 +212,53 @@ namespace Sass { Compound_Selector_Obj unified = r_last_head->unify_with(l_last_head); // abort if we could not unify heads - if (unified == 0) return 0; + if (unified == nullptr) return nullptr; - // check for universal (star: `*`) selector - bool is_universal = l_last_head->is_universal() || - r_last_head->is_universal(); + // move the head + if (l_last_head->is_universal()) l_last->head({}); + r_last->head(unified); - if (is_universal) - { - // move the head - l_last->head({}); - r_last->head(unified); - } + #ifdef DEBUG_UNIFY + std::cerr << "> " << debug_call << " before weave: lhs=" << this->to_string() << " rhs=" << rhs->to_string() << std::endl; + #endif // create nodes from both selectors Node lhsNode = complexSelectorToNode(this); - Node rhsNode = complexSelectorToNode(other); - - // overwrite universal base - if (!is_universal) - { - // create some temporaries to convert to node - Complex_Selector_Obj fake = unified->to_complex(); - Node unified_node = complexSelectorToNode(fake); - // add to permutate the list? - rhsNode.plus(unified_node); - } + Node rhsNode = complexSelectorToNode(rhs); + + // Complex_Selector_Obj fake = unified->to_complex(); + // Node unified_node = complexSelectorToNode(fake); + // // add to permutate the list? + // rhsNode.plus(unified_node); // do some magic we inherit from node and extend Node node = subweave(lhsNode, rhsNode); - Selector_List_Obj result = SASS_MEMORY_NEW(Selector_List, pstate()); - NodeDequePtr col = node.collection(); // move from collection to list - for (NodeDeque::iterator it = col->begin(), end = col->end(); it != end; it++) - { result->append(nodeToComplexSelector(Node::naiveTrim(*it))); } + Selector_List_Obj result = SASS_MEMORY_NEW(Selector_List, pstate(), node.collection()->size()); + for (auto &item : *node.collection()) { + result->append(nodeToComplexSelector(Node::naiveTrim(item))); + } - // only return if list has some entries - return result->length() ? result.detach() : 0; + #ifdef DEBUG_UNIFY + std::cerr << "> " << debug_call << " = " << result->to_string() << std::endl; + #endif + // only return if list has some entries + return result->length() ? result.detach() : nullptr; } Selector_List_Ptr Selector_List::unify_with(Selector_List_Ptr rhs) { + #ifdef DEBUG_UNIFY + const std::string debug_call = "unify(List[" + this->to_string() + "], List[" + rhs->to_string() + "])"; + std::cerr << debug_call << std::endl; + #endif + std::vector result; // Unify all of children with RHS's children, storing the results in `unified_complex_selectors` for (Complex_Selector_Obj& seq1 : elements()) { for (Complex_Selector_Obj& seq2 : rhs->elements()) { - Selector_List_Obj unified = seq1->unify_with(seq2); + Complex_Selector_Obj seq1_copy = SASS_MEMORY_CLONE(seq1); + Complex_Selector_Obj seq2_copy = SASS_MEMORY_CLONE(seq2); + Selector_List_Obj unified = seq1_copy->unify_with(seq2_copy); if (unified) { result.reserve(result.size() + unified->length()); std::copy(unified->begin(), unified->end(), std::back_inserter(result)); @@ -234,6 +271,9 @@ namespace Sass { for (Complex_Selector_Obj& sel : result) { final_result->append(sel); } + #ifdef DEBUG_UNIFY + std::cerr << "> " << debug_call << " = " << final_result->to_string() << std::endl; + #endif return final_result; } diff --git a/src/ast_selectors.cpp b/src/ast_selectors.cpp index 00a1b4fb3..76547d86b 100644 --- a/src/ast_selectors.cpp +++ b/src/ast_selectors.cpp @@ -8,6 +8,7 @@ #include "color_maps.hpp" #include "ast_fwd_decl.hpp" #include "ast_selectors.hpp" +#include #include #include #include @@ -135,8 +136,8 @@ namespace Sass { if (hash_ == 0) { hash_combine(hash_, std::hash()(SELECTOR)); hash_combine(hash_, std::hash()(simple_type())); - hash_combine(hash_, std::hash()(ns())); - hash_combine(hash_, std::hash()(name())); + if (!name_.empty()) hash_combine(hash_, std::hash()(name())); + if (has_ns_) hash_combine(hash_, std::hash()(ns())); } return hash_; } @@ -148,16 +149,7 @@ namespace Sass { // namespace compare functions bool Simple_Selector::is_ns_eq(const Simple_Selector& r) const { - // https://github.com/sass/sass/issues/2229 - if ((has_ns_ == r.has_ns_) || - (has_ns_ && ns_.empty()) || - (r.has_ns_ && r.ns_.empty()) - ) { - if (ns_.empty() && r.ns() == "*") return false; - else if (r.ns().empty() && ns() == "*") return false; - else return ns() == r.ns(); - } - return false; + return has_ns_ == r.has_ns_ && ns_ == r.ns_; } // namespace query functions @@ -166,11 +158,6 @@ namespace Sass { return has_ns_ && ns_ == "*"; } - bool Simple_Selector::has_universal_ns() const - { - return !has_ns_ || ns_ == "*"; - } - bool Simple_Selector::is_empty_ns() const { return !has_ns_ || ns_ == ""; @@ -284,7 +271,7 @@ namespace Sass { Class_Selector::Class_Selector(const Class_Selector* ptr) : Simple_Selector(ptr) { simple_type(CLASS_SEL); } - + unsigned long Class_Selector::specificity() const { return Constants::Specificity_Class; @@ -299,7 +286,7 @@ namespace Sass { Id_Selector::Id_Selector(const Id_Selector* ptr) : Simple_Selector(ptr) { simple_type(ID_SEL); } - + unsigned long Id_Selector::specificity() const { return Constants::Specificity_ID; @@ -511,53 +498,38 @@ namespace Sass { bool Compound_Selector::is_superselector_of(Compound_Selector_Ptr_Const rhs, std::string wrapping) const { - Compound_Selector_Ptr_Const lhs = this; - Simple_Selector_Ptr lbase = lhs->base(); - Simple_Selector_Ptr rbase = rhs->base(); - // Check if pseudo-elements are the same between the selectors - - std::set lpsuedoset, rpsuedoset; - for (size_t i = 0, L = length(); i < L; ++i) { - if ((*this)[i]->is_pseudo_element()) { - std::string pseudo((*this)[i]->to_string()); - pseudo = pseudo.substr(pseudo.find_first_not_of(":")); // strip off colons to ensure :after matches ::after since ruby sass is forgiving - lpsuedoset.insert(pseudo); - } - } - for (size_t i = 0, L = rhs->length(); i < L; ++i) - { - if ((*rhs)[i]->is_pseudo_element()) { - std::string pseudo((*rhs)[i]->to_string()); - pseudo = pseudo.substr(pseudo.find_first_not_of(":")); // strip off colons to ensure :after matches ::after since ruby sass is forgiving - rpsuedoset.insert(pseudo); + std::array, 2> pseudosets; + std::array compounds = {{this, rhs}}; + for (int i = 0; i < 2; ++i) { + for (const Simple_Selector_Obj& el : compounds[i]->elements()) { + if (el->is_pseudo_element()) { + std::string pseudo(el->to_string()); + // strip off colons to ensure :after matches ::after since ruby sass is forgiving + pseudosets[i].insert(pseudo.substr(pseudo.find_first_not_of(":"))); + } + } } + if (pseudosets[0] != pseudosets[1]) return false; } - if (lpsuedoset != rpsuedoset) { - return false; - } - - // replaced compare without stringification - // https://github.com/sass/sass/issues/2229 - SelectorSet lset, rset; - if (lbase && rbase) { - if (*lbase == *rbase) { - // create ordered sets for includes query - lset.insert(this->begin(), this->end()); - rset.insert(rhs->begin(), rhs->end()); - return std::includes(rset.begin(), rset.end(), lset.begin(), lset.end(), OrderSelectors); + Simple_Selector_Ptr_Const lbase = this->base(); + Simple_Selector_Ptr_Const rbase = rhs->base(); + if (lbase && rbase) { + return *lbase == *rbase && + contains_all(std::unordered_set(rhs->begin(), rhs->end()), + std::unordered_set(this->begin(), this->end())); } - return false; } + std::unordered_set lset; for (size_t i = 0, iL = length(); i < iL; ++i) { - Selector_Obj wlhs = (*this)[i]; + Selector_Ptr_Const wlhs = (*this)[i].ptr(); // very special case for wrapped matches selector - if (Wrapped_Selector_Obj wrapped = Cast(wlhs)) { + if (Wrapped_Selector_Ptr_Const wrapped = Cast(wlhs)) { if (wrapped->name() == ":not") { if (Selector_List_Obj not_list = Cast(wrapped->selector())) { if (not_list->is_superselector_of(rhs, wrapped->name())) return false; @@ -576,7 +548,7 @@ namespace Sass { } } } - Simple_Selector_Ptr rhs_sel = NULL; + Simple_Selector_Ptr rhs_sel = nullptr; if (rhs->elements().size() > i) rhs_sel = (*rhs)[i]; if (Wrapped_Selector_Ptr wrapped_r = Cast(rhs_sel)) { if (wrapped->name() == wrapped_r->name()) { @@ -588,6 +560,9 @@ namespace Sass { lset.insert(wlhs); } + if (lset.empty()) return true; + + std::unordered_set rset; for (size_t n = 0, nL = rhs->length(); n < nL; ++n) { Selector_Obj r = (*rhs)[n]; @@ -611,14 +586,7 @@ namespace Sass { rset.insert(r); } - //for (auto l : lset) { cerr << "l: " << l << endl; } - //for (auto r : rset) { cerr << "r: " << r << endl; } - - if (lset.empty()) return true; - - // return true if rset contains all the elements of lset - return std::includes(rset.begin(), rset.end(), lset.begin(), lset.end(), OrderSelectors); - + return contains_all(rset, lset); } bool Compound_Selector::is_universal() const @@ -761,10 +729,13 @@ namespace Sass { size_t Complex_Selector::hash() const { if (hash_ == 0) { - hash_combine(hash_, std::hash()(SELECTOR)); - hash_combine(hash_, std::hash()(combinator_)); - if (head_) hash_combine(hash_, head_->hash()); + if (head_) { + hash_combine(hash_, head_->hash()); + } else { + hash_combine(hash_, std::hash()(SELECTOR)); + } if (tail_) hash_combine(hash_, tail_->hash()); + if (combinator_ != ANCESTOR_OF) hash_combine(hash_, std::hash()(combinator_)); } return hash_; } @@ -1445,8 +1416,11 @@ namespace Sass { size_t Selector_List::hash() const { if (Selector::hash_ == 0) { - hash_combine(Selector::hash_, std::hash()(SELECTOR)); - hash_combine(Selector::hash_, Vectorized::hash()); + if (empty()) { + hash_combine(Selector::hash_, std::hash()(SELECTOR)); + } else { + hash_combine(Selector::hash_, Vectorized::hash()); + } } return Selector::hash_; } diff --git a/src/ast_selectors.hpp b/src/ast_selectors.hpp index 9f5270799..ee4c3a306 100644 --- a/src/ast_selectors.hpp +++ b/src/ast_selectors.hpp @@ -109,10 +109,10 @@ namespace Sass { PLACEHOLDER_SEL, }; public: - ADD_CONSTREF(std::string, ns) - ADD_CONSTREF(std::string, name) + HASH_CONSTREF(std::string, ns) + HASH_CONSTREF(std::string, name) ADD_PROPERTY(Simple_Type, simple_type) - ADD_PROPERTY(bool, has_ns) + HASH_PROPERTY(bool, has_ns) public: Simple_Selector(ParserState pstate, std::string n = ""); Simple_Selector(const Simple_Selector* ptr); @@ -123,7 +123,6 @@ namespace Sass { bool is_ns_eq(const Simple_Selector& r) const; // namespace query functions bool is_universal_ns() const; - bool has_universal_ns() const; bool is_empty_ns() const; bool has_empty_ns() const; bool has_qualified_ns() const; diff --git a/src/extend.cpp b/src/extend.cpp index 86c201aa4..98940573c 100644 --- a/src/extend.cpp +++ b/src/extend.cpp @@ -1851,8 +1851,7 @@ namespace Sass { // Ruby Equivalent: flatten Node flattened(flatten(trimmed, 1)); - DEBUG_PRINTLN(EXTEND_COMPLEX, ">>>>> EXTENDED: " << extendedSelectors) - DEBUG_PRINTLN(EXTEND_COMPLEX, "EXTEND COMPLEX END: " << complexSelector) + DEBUG_PRINTLN(EXTEND_COMPLEX, "FLATTENED: " << flattened) // memory results in a map table - since extending is very expensive memoizeComplex.insert(std::pair(selector, flattened)); diff --git a/src/parser.cpp b/src/parser.cpp index 04937034a..04b3d3e07 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -648,7 +648,7 @@ namespace Sass { // parse mandatory arguments call->arguments(parse_arguments()); // parse using and optional block parameters - bool has_parameters = lex< kwd_using >(); + bool has_parameters = lex< kwd_using >() != nullptr; if (has_parameters) { if (!peek< exactly<'('> >()) css_error("Invalid CSS", " after ", ": expected \"(\", was "); diff --git a/src/util.hpp b/src/util.hpp index 6c98f91c1..3c463eed1 100644 --- a/src/util.hpp +++ b/src/util.hpp @@ -35,6 +35,15 @@ namespace Sass { bool peek_linefeed(const char* start); + // Returns true iff `elements` ⊆ `container`. + template + bool contains_all(C container, T elements) { + for (const auto &el : elements) { + if (container.find(el) == container.end()) return false; + } + return true; + } + // C++20 `starts_with` equivalent. // See https://en.cppreference.com/w/cpp/string/basic_string/starts_with inline bool starts_with(const std::string& str, const char* prefix, size_t prefix_len) {