//===--- HomotopyReduction.cpp - Higher-dimensional term rewriting --------===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2021 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// // // This file implements the algorithm for computing a minimal set of rules from // a confluent rewrite system. A minimal set of rules is: // // 1) Large enough that computing the confluent completion produces the original // rewrite system; // // 2) Small enough that no further rules can be deleted without changing the // resulting confluent rewrite system. // // The main entry point here is RewriteSystem::minimizeRewriteSystem(). // // Redundant rules are detected by analyzing the set of rewrite loops computed // by the completion procedure. See RewriteLoop.cpp for a discussion of rewrite // loops. // // If a rewrite rule appears exactly once in a loop and without context, the // loop witnesses a redundancy; the rewrite rule is equivalent to traveling // around the loop "in the other direction". This rewrite rule and the // corresponding rewrite loop can be deleted. // // Any occurrence of the rule in the remaining loops is replaced with the // alternate definition obtained by splitting the loop that witnessed the // redundancy. // // Iterating this process eventually produces a minimal set of rewrite rules. // // For a description of the general algorithm, see "A Homotopical Completion // Procedure with Applications to Coherence of Monoids", // https://hal.inria.fr/hal-00818253. // // Note that in the world of Swift, rewrite rules for introducing associated // type symbols are marked 'permanent'; they are always re-added when a new // rewrite system is built from a minimal generic signature, so instead of // deleting them it is better to leave them in place in case it allows other // rules to be deleted instead. // // Also, for a conformance rule (V.[P] => V) to be redundant, a stronger // condition is needed than appearing once in a loop and without context; // the rule must not be a _minimal conformance_. The algorithm for computing // minimal conformances is implemented in MinimalConformances.cpp. // //===----------------------------------------------------------------------===// #include "swift/AST/Type.h" #include "swift/Basic/Assertions.h" #include "swift/Basic/Range.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/DenseSet.h" #include "llvm/ADT/SmallVector.h" #include "llvm/Support/Debug.h" #include "llvm/Support/raw_ostream.h" #include #include "PropertyMap.h" #include "RewriteContext.h" #include "RewriteSystem.h" using namespace swift; using namespace rewriting; /// If a rewrite loop contains an explicit rule in empty context, propagate the /// explicit bit to all other rules appearing in empty context within the same /// loop. /// /// When computing minimal conformances we prefer to eliminate non-explicit /// rules, as a heuristic to ensure that minimized conformance requirements /// remain in the same protocol as originally written, in cases where they can /// be moved between protocols. /// /// However, conformance rules can also be written in a non-canonical way. /// /// Most conformance requirements are non-canonical, since the original /// requirements use unresolved types. For example, a requirement 'Self.X.Y : Q' /// inside a protocol P will lower to a rewrite rule /// /// [P].X.Y.[Q] => [P].X.Y /// /// Completion will then add a new rule that looks something like this, using /// associated type symbols: /// /// [P:X].[P2:Y].[Q] => [P:X].[P2:Y] /// /// Furthermore, if [P:X].[P2:Y] simplies to some other term, such as [P:Z], /// there will be yet another rule added by completion: /// /// [P:Z].[Q] => [P:Z] /// /// The new rules are related to the original rule via rewrite loops where /// both rules appear in empty context. This algorithm will propagate the /// explicit bit from the original rule to the canonical rule. void RewriteSystem::propagateExplicitBits() { for (const auto &loop : Loops) { auto rulesInEmptyContext = loop.findRulesAppearingOnceInEmptyContext(*this); bool sawExplicitRule = false; for (unsigned ruleID : rulesInEmptyContext) { const auto &rule = getRule(ruleID); if (rule.isExplicit()) sawExplicitRule = true; } if (sawExplicitRule) { for (unsigned ruleID : rulesInEmptyContext) { auto &rule = getRule(ruleID); if (!rule.isPermanent() && !rule.isExplicit()) rule.markExplicit(); } } } } /// Find concrete type or superclass rules where the right hand side occurs as a /// proper prefix of one of its substitutions. /// /// eg, (T.[concrete: G] => T). void RewriteSystem::computeRecursiveRules() { for (unsigned ruleID = FirstLocalRule, e = Rules.size(); ruleID < e; ++ruleID) { auto &rule = getRule(ruleID); if (rule.isPermanent() || rule.isRedundant()) continue; auto optSymbol = rule.isPropertyRule(); if (!optSymbol) continue; auto kind = optSymbol->getKind(); if (kind != Symbol::Kind::ConcreteType && kind != Symbol::Kind::Superclass) { continue; } auto rhs = rule.getRHS(); for (auto term : optSymbol->getSubstitutions()) { if (term.size() > rhs.size() && std::equal(rhs.begin(), rhs.end(), term.begin())) { RecursiveRules.push_back(ruleID); rule.markRecursive(); break; } } } } /// Find a rule to delete by looking through all loops for rewrite rules appearing /// once in empty context. Returns a pair consisting of a loop ID and a rule ID, /// otherwise returns None. /// /// Minimization performs three passes over the rewrite system. /// /// 1) First, rules that are not conformance rules are deleted, with /// \p redundantConformances equal to nullptr. /// /// 2) Second, minimal conformances are computed. /// /// 3) Finally, redundant conformance rules are deleted, with /// \p redundantConformances equal to the set of conformance rules that are /// not minimal conformances. std::optional> RewriteSystem::findRuleToDelete(EliminationPredicate isRedundantRuleFn) { SmallVector, 2> redundancyCandidates; for (unsigned loopID : indices(Loops)) { auto &loop = Loops[loopID]; if (loop.isDeleted()) continue; // Delete loops that don't contain any rewrite rules in empty context, // since such loops do not yield any elimination candidates. if (!loop.isUseful(*this)) { if (Debug.contains(DebugFlags::HomotopyReduction)) { llvm::dbgs() << "** Deleting useless loop #" << loopID << ": "; loop.dump(llvm::dbgs(), *this); llvm::dbgs() << "\n"; } loop.markDeleted(); continue; } for (unsigned ruleID : loop.findRulesAppearingOnceInEmptyContext(*this)) { redundancyCandidates.emplace_back(loopID, ruleID); } } std::optional> found; if (Debug.contains(DebugFlags::HomotopyReduction)) { llvm::dbgs() << "\n"; } for (const auto &pair : redundancyCandidates) { unsigned loopID = pair.first; unsigned ruleID = pair.second; const auto &loop = Loops[loopID]; const auto &rule = getRule(ruleID); // We should not find a rule that has already been marked redundant // here; it should have already been replaced with a rewrite path // in all homotopy generators. ASSERT(!rule.isRedundant()); // Associated type introduction rules are 'permanent'. They're // not worth eliminating since they are re-added every time; it // is better to find other candidates to eliminate in the same // loop instead. if (rule.isPermanent()) continue; // Homotopy reduction runs multiple passes with different filters to // prioritize the deletion of certain rules ahead of others. Apply // the filter now. if (!isRedundantRuleFn(loopID, ruleID)) { if (Debug.contains(DebugFlags::HomotopyReductionDetail)) { llvm::dbgs() << "** Skipping rule " << rule << " from loop #" << loopID << "\n"; } continue; } if (Debug.contains(DebugFlags::HomotopyReductionDetail)) { llvm::dbgs() << "** Candidate rule " << rule << " from loop #" << loopID << "\n"; } if (!found) { found = pair; continue; } // 'rule' is the candidate rule; 'otherRule' is the best rule to eliminate // we've found so far. const auto &otherRule = getRule(found->second); const auto &otherLoop = Loops[found->first]; { // If one of the rules was a concrete unification projection, prefer to // eliminate the *other* rule. // // For example, if 'X.T == G' is implied by the conformance on X, // and the following three rules are defined in the current protocol: // // a) X.T == G // b) X.U == Int // c) X.V == W // // Then we can either eliminate a) alone, or b) and c). Since b) and c) // are projections, they are "simpler", and we would rather keep both and // eliminate a). unsigned projectionCount = loop.getProjectionCount(*this); unsigned otherProjectionCount = otherLoop.getProjectionCount(*this); if (projectionCount != otherProjectionCount) { if (projectionCount < otherProjectionCount) found = pair; continue; } } { // If one of the rules is a concrete type requirement, prefer to // eliminate the *other* rule. bool ruleIsConcrete = rule.getLHS().back().hasSubstitutions(); bool otherRuleIsConcrete = otherRule.getLHS().back().hasSubstitutions(); if (ruleIsConcrete != otherRuleIsConcrete) { if (otherRuleIsConcrete) found = pair; continue; } } { // If both are concrete type requirements, prefer to eliminate the // one with the more deeply nested type. unsigned ruleNesting = rule.getNestingAndSize().first; unsigned otherRuleNesting = otherRule.getNestingAndSize().first; if (ruleNesting != otherRuleNesting) { if (ruleNesting > otherRuleNesting) found = pair; continue; } } { // Otherwise, perform a shortlex comparison on (LHS, RHS). std::optional comparison = rule.compare(otherRule, Context); if (!comparison.has_value()) { // Two rules (T.[C] => T) and (T.[C'] => T) are incomparable if // C and C' are superclass, concrete type or concrete conformance // symbols. continue; } if (*comparison == 0) { // Given two rewrite loops that both eliminate the same rule, prefer // the one that was not recorded by substitution simplification; // substitution simplification rules contain the projections in // context, which then prevents the projections from being eliminated. // // An example is if you have two rules implied by conformances on X, // // a) X.T == G // b) X.T == G // // then the induced rule Y == Z is a projection. // // The rule X.T == G can be eliminated with a loop that begins at // X.T.[concrete: G] followed by a decomposition and rewrite of // Y into Z, finally followed by an inverse decomposition back to // X.T.[concrete: G]. // // However, if we can eliminate G via some other loop, we prefer // to do that, since that might *also* allow us to eliminate Y == Z. unsigned decomposeCount = loop.getDecomposeCount(*this); unsigned otherDecomposeCount = otherLoop.getDecomposeCount(*this); if (decomposeCount != otherDecomposeCount) { if (decomposeCount < otherDecomposeCount) found = pair; continue; } } if (*comparison > 0) { // Otherwise, if the new rule is less canonical than the best one so // far, it becomes the new candidate for elimination. found = pair; continue; } } } return found; } /// Delete a rewrite rule that is known to be redundant, replacing all /// occurrences of the rule in all loops with the replacement path. void RewriteSystem::deleteRule(unsigned ruleID, const RewritePath &replacementPath) { // Replace all occurrences of the rule with the replacement path in // all remaining rewrite loops. for (unsigned loopID : indices(Loops)) { auto &loop = Loops[loopID]; if (loop.isDeleted()) continue; bool changed = loop.Path.replaceRuleWithPath(ruleID, replacementPath); if (!changed) continue; if (Context.getASTContext().LangOpts.EnableRequirementMachineLoopNormalization) { loop.computeNormalForm(*this); } // The loop's path has changed, so we must invalidate the cached // result of findRulesAppearingOnceInEmptyContext(). loop.markDirty(); if (Debug.contains(DebugFlags::HomotopyReductionDetail)) { llvm::dbgs() << "** Updated loop #" << loopID << ": "; loop.dump(llvm::dbgs(), *this); llvm::dbgs() << "\n"; } } // Record the redundant rule along with its replacement path. RedundantRules.emplace_back(ruleID, replacementPath); } void RewriteSystem::performHomotopyReduction( EliminationPredicate isRedundantRuleFn) { while (true) { auto optPair = findRuleToDelete(isRedundantRuleFn); // If no redundant rules remain which can be eliminated by this pass, stop. if (!optPair) break; unsigned loopID = optPair->first; unsigned ruleID = optPair->second; auto &loop = Loops[loopID]; auto replacementPath = loop.Path.splitCycleAtRule(ruleID); loop.markDeleted(); auto &rule = getRule(ruleID); if (Debug.contains(DebugFlags::HomotopyReduction)) { llvm::dbgs() << "** Deleting rule " << rule << " from loop #" << loopID << "\n"; llvm::dbgs() << "* Replacement path: "; MutableTerm mutTerm(getRule(ruleID).getLHS()); replacementPath.dump(llvm::dbgs(), mutTerm, *this); llvm::dbgs() << "\n"; } rule.markRedundant(); deleteRule(ruleID, replacementPath); } } /// Use the loops to delete redundant rewrite rules via a series of Tietze /// transformations, updating and simplifying existing loops as each rule /// is deleted. /// /// Redundant rules are mutated to set their isRedundant() bit. void RewriteSystem::minimizeRewriteSystem(const PropertyMap &map) { if (Debug.contains(DebugFlags::HomotopyReduction)) { llvm::dbgs() << "-----------------------------\n"; llvm::dbgs() << "- Minimizing rewrite system -\n"; llvm::dbgs() << "-----------------------------\n"; } ASSERT(Complete); ASSERT(!Minimized); ASSERT(!Frozen); Minimized = 1; propagateExplicitBits(); if (Context.getASTContext().LangOpts.EnableRequirementMachineLoopNormalization) { for (auto &loop : Loops) { loop.computeNormalForm(*this); } } // First pass: // - Eliminate all LHS-simplified non-conformance rules. // - Eliminate all RHS-simplified and substitution-simplified rules. // // An example of a conformance rule that is LHS-simplified but not // RHS-simplified is (T.[P] => T) where T is irreducible, but there // is a rule (V.[P] => V) for some V with T == U.V. // // Such conformance rules can still be minimal, as part of a hack to // maintain compatibility with the GenericSignatureBuilder's minimization // algorithm. if (Debug.contains(DebugFlags::HomotopyReduction)) { llvm::dbgs() << "------------------------------\n"; llvm::dbgs() << "First pass: simplified rules -\n"; llvm::dbgs() << "------------------------------\n"; } performHomotopyReduction([&](unsigned loopID, unsigned ruleID) -> bool { const auto &rule = getRule(ruleID); if (rule.isLHSSimplified() && !rule.isAnyConformanceRule()) return true; if (rule.isRHSSimplified() || rule.isSubstitutionSimplified()) return true; return false; }); // Second pass: // - Eliminate all rules with unresolved symbols which were *not* // simplified. // // Two examples of such rules: // // - (T.X => T.[P:X]) obtained from resolving the overlap between // (T.[P] => T) and ([P].X => [P:X]). // // - (T.X.[concrete: C] => T.X) obtained from resolving the overlap // between (T.[P] => T) and a protocol typealias rule // ([P].X.[concrete: C] => [P].X). if (Debug.contains(DebugFlags::HomotopyReduction)) { llvm::dbgs() << "-------------------------------\n"; llvm::dbgs() << "Second pass: unresolved rules -\n"; llvm::dbgs() << "-------------------------------\n"; } performHomotopyReduction([&](unsigned loopID, unsigned ruleID) -> bool { const auto &rule = getRule(ruleID); if (rule.containsNameSymbols()) return true; return false; }); // Now compute a set of minimal conformances. // // FIXME: For now this just produces a set of redundant conformances, but // it should actually output the canonical minimal conformance equation // for each non-minimal conformance. We can then use information to // compute conformance access paths, instead of the current "brute force" // algorithm used for that purpose. llvm::DenseSet redundantConformances; computeMinimalConformances(map, redundantConformances); // Third pass: Eliminate all non-minimal conformance rules. if (Debug.contains(DebugFlags::HomotopyReduction)) { llvm::dbgs() << "-------------------------------------------\n"; llvm::dbgs() << "Third pass: non-minimal conformance rules -\n"; llvm::dbgs() << "-------------------------------------------\n"; } performHomotopyReduction([&](unsigned loopID, unsigned ruleID) -> bool { const auto &rule = getRule(ruleID); if (rule.isAnyConformanceRule() && redundantConformances.count(ruleID)) return true; return false; }); // Fourth pass: Eliminate all remaining redundant non-conformance rules. if (Debug.contains(DebugFlags::HomotopyReduction)) { llvm::dbgs() << "----------------------------------------\n"; llvm::dbgs() << "Fourth pass: all other redundant rules -\n"; llvm::dbgs() << "----------------------------------------\n"; } performHomotopyReduction([&](unsigned loopID, unsigned ruleID) -> bool { const auto &loop = Loops[loopID]; const auto &rule = getRule(ruleID); if (rule.isProtocolTypeAliasRule()) return true; if (!loop.hasConcreteTypeAliasRule(*this) && !rule.isAnyConformanceRule()) return true; return false; }); computeRecursiveRules(); // Check invariants after homotopy reduction. verifyRewriteLoops(); verifyRedundantConformances(redundantConformances); verifyMinimizedRules(redundantConformances); if (Debug.contains(DebugFlags::RedundantRules)) { llvm::dbgs() << "\nRedundant rules:\n"; for (const auto &pair : RedundantRules) { const auto &rule = getRule(pair.first); llvm::dbgs() << "- (" << rule.getLHS() << " => " << rule.getRHS() << ") ::== "; MutableTerm lhs(rule.getLHS()); pair.second.dump(llvm::dbgs(), lhs, *this); llvm::dbgs() << "\n"; if (Debug.contains(DebugFlags::RedundantRulesDetail)) { llvm::dbgs() << "\n"; pair.second.dumpLong(llvm::dbgs(), lhs, *this); llvm::dbgs() << "\n\n"; } } } } /// Returns flags indicating if the rewrite system has unresolved or /// conflicting rules in our minimization domain. If these flags are /// set, we do not install this rewrite system in the rewrite context /// after minimization. Instead, we will rebuild a new rewrite system /// from the minimized requirements. GenericSignatureErrors RewriteSystem::getErrors() const { ASSERT(Complete); ASSERT(Minimized); GenericSignatureErrors result; if (!ConflictingRules.empty()) result |= GenericSignatureErrorFlags::HasInvalidRequirements; for (const auto &rule : getLocalRules()) { if (rule.isPermanent()) continue; // The conditional requirement inference feature imports new protocol // components after the basic rewrite system is already built, so that's // why we end up with imported rules that appear to be in the local rules // slice. Those rules are well-formed, but their isRedundant() bit isn't // set, so we must ignore them here. if (!isInMinimizationDomain(rule.getLHS().getRootProtocol())) continue; if (!rule.isRedundant()) { if (!rule.isProtocolTypeAliasRule() && rule.containsNameSymbols()) result |= GenericSignatureErrorFlags::HasInvalidRequirements; } if (rule.isRecursive()) result |= GenericSignatureErrorFlags::HasInvalidRequirements; if (!rule.isRedundant()) { if (auto property = rule.isPropertyRule()) { if (property->getKind() == Symbol::Kind::ConcreteConformance) result |= GenericSignatureErrorFlags::HasConcreteConformances; if (property->hasSubstitutions() && property->containsNameSymbols()) result |= GenericSignatureErrorFlags::HasInvalidRequirements; } } } return result; } /// Collect all non-permanent, non-redundant rules whose domain is equal to /// one of the protocols in the connected component represented by this /// rewrite system. /// /// These rules form the requirement signatures of these protocols. llvm::DenseMap RewriteSystem::getMinimizedProtocolRules() const { ASSERT(Minimized); ASSERT(!Protos.empty()); llvm::DenseMap rules; for (unsigned ruleID = FirstLocalRule, e = Rules.size(); ruleID < e; ++ruleID) { const auto &rule = getRule(ruleID); if (rule.isPermanent() || rule.isRedundant() || rule.isConflicting()) continue; const auto *proto = rule.getLHS().getRootProtocol(); if (!isInMinimizationDomain(proto)) continue; if (rule.isProtocolTypeAliasRule()) { if (auto property = rule.isPropertyRule()) { if (property->containsNameSymbols()) continue; } else if (rule.getRHS().containsNameSymbols()) { continue; } rules[proto].TypeAliases.push_back(ruleID); } else { if (rule.containsNameSymbols()) continue; rules[proto].Requirements.push_back(ruleID); } } return rules; } /// Collect all non-permanent, non-redundant rules whose left hand side /// begins with a generic parameter symbol. /// /// These rules form the top-level generic signature for this rewrite system. std::vector RewriteSystem::getMinimizedGenericSignatureRules() const { ASSERT(Minimized); ASSERT(Protos.empty()); std::vector rules; for (unsigned ruleID = FirstLocalRule, e = Rules.size(); ruleID < e; ++ruleID) { const auto &rule = getRule(ruleID); if (rule.isPermanent() || rule.isRedundant() || rule.isConflicting() || rule.containsNameSymbols()) { continue; } if (rule.getLHS()[0].getKind() != Symbol::Kind::PackElement && rule.getLHS()[0].getKind() != Symbol::Kind::GenericParam) continue; rules.push_back(ruleID); } return rules; } /// Verify that each loop begins and ends at its basepoint. void RewriteSystem::verifyRewriteLoops() const { for (const auto &loop : Loops) { loop.verify(*this); } } /// Assert if homotopy reduction failed to eliminate a redundant conformance, /// since this suggests a misunderstanding on my part. void RewriteSystem::verifyRedundantConformances( const llvm::DenseSet &redundantConformances) const { for (unsigned ruleID : redundantConformances) { const auto &rule = getRule(ruleID); ASSERT(!rule.isPermanent() && "Permanent rule cannot be redundant"); ASSERT(!rule.isIdentityConformanceRule() && "Identity conformance cannot be redundant"); ASSERT(rule.isAnyConformanceRule() && "Redundant conformance is not a conformance rule?"); if (!rule.isRedundant()) { ABORT([&](auto &out) { out << "Homotopy reduction did not eliminate redundant conformance?\n"; out << "(#" << ruleID << ") " << rule << "\n\n"; dump(out); }); } } } // Assert if homotopy reduction failed to eliminate a rewrite rule it was // supposed to delete. void RewriteSystem::verifyMinimizedRules( const llvm::DenseSet &redundantConformances) const { unsigned redundantRuleCount = 0; for (unsigned ruleID = FirstLocalRule, e = Rules.size(); ruleID < e; ++ruleID) { const auto &rule = getRule(ruleID); // Ignore the rewrite rule if it is not part of our minimization domain. if (!isInMinimizationDomain(rule.getLHS().getRootProtocol())) { if (rule.isRedundant()) { ABORT([&](auto &out) { out << "Redundant rule outside minimization domain: " << rule << "\n\n"; dump(out); }); } continue; } // Note that sometimes permanent rules can be simplified, but they can never // be redundant. if (rule.isPermanent()) { if (rule.isRedundant()) { ABORT([&](auto &out) { out << "Permanent rule is redundant: " << rule << "\n\n"; dump(out); }); } continue; } if (rule.isRedundant()) ++redundantRuleCount; // LHS-simplified rules should be redundant, unless they're protocol // conformance rules, which unfortunately might not be redundant, because // we try to keep them in the original protocol definition for // compatibility with the GenericSignatureBuilder's minimization algorithm. if (rule.isLHSSimplified() && !rule.isRedundant() && !rule.isProtocolConformanceRule()) { ABORT([&](auto &out) { out << "Simplified rule is not redundant: " << rule << "\n\n"; dump(out); }); } // RHS-simplified and substitution-simplified rules should be redundant. if ((rule.isRHSSimplified() || rule.isSubstitutionSimplified()) && !rule.isRedundant()) { ABORT([&](auto &out) { out << "Simplified rule is not redundant: " << rule << "\n\n"; dump(out); }); } if (rule.isRedundant() && rule.isAnyConformanceRule() && !rule.isRHSSimplified() && !rule.isSubstitutionSimplified() && !rule.containsNameSymbols() && !redundantConformances.count(ruleID)) { ABORT([&](auto &out) { out << "Minimal conformance is redundant: " << rule << "\n\n"; dump(out); }); } } if (RedundantRules.size() != redundantRuleCount) { ABORT([&](auto &out) { out << "Expected " << RedundantRules.size() << " redundant rules " << "but counted " << redundantRuleCount << "\n"; dump(out); }); } // Replacement paths for redundant rules can only reference other redundant // rules if those redundant rules were made redundant later, ie if they // appear later in the array. llvm::DenseSet laterRedundantRules; for (const auto &pair : llvm::reverse(RedundantRules)) { const auto &rule = getRule(pair.first); if (!rule.isRedundant()) { ABORT([&](auto &out) { out << "Recorded replacement path for non-redundant rule " << rule << "\n"; dump(out); }); } for (const auto &step : pair.second) { if (step.Kind == RewriteStep::Rule) { unsigned otherRuleID = step.getRuleID(); const auto &otherRule = getRule(otherRuleID); if (otherRule.isRedundant() && !laterRedundantRules.count(otherRuleID)) { ABORT([&](auto &out) { out << "Redundant requirement path contains a redundant rule " << otherRule << "\n"; dump(out); }); } } } laterRedundantRules.insert(pair.first); } }