Files
swift-mirror/lib/AST/RequirementMachine/SimplifySubstitutions.cpp
Josh Soref 81d3ad76ac Spelling ast (#42463)
* spelling: accessor

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: accommodates

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: argument

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: associated

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: availability

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: available

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: belongs

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: bookkeeping

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: building

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: clazz

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: clonable

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: closure

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: concatenated

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: conformance

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: context

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: conversion

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: correspondence

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: declarations

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: declared

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: defining

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: delayed

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: dependency

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: deployed

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: descendants

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: diagnose

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: diagnostic

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: equitable

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: evaluation

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: exclusivity

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: existence

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: existential

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: explicit

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: expressed

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: for

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: foreign

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: function

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: identifier

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: implicit

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: indices

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: information

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: instance

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: interchangeable

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: interface

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: introduced

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: invalid

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: kind-in

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: least

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: library

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: location

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: namespace

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: necessary

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: nonexistent

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: not

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: number

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: obtains

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: occurs

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: opaque

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: overridden

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: parameter

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: precede

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: preceding

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: property

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: protocol

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: qualified

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: recognized

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: recursively

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: references

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: relaxing

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: represented

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: request

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: requirement

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: requirements

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: retrieve

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: returned

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: satisfied

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: satisfy

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: scanner

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: siblings

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: simplified

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: something

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: source

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: specializations

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: specially

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: statement

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: stripped

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: structure

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: substitution

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: the

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: transform

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: transformed

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: transitively

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: transparent

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: typecheck

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: unknown

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: unlabeled

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: unqualified

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: whether

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: with

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: scanner

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

Co-authored-by: Josh Soref <jsoref@users.noreply.github.com>
2022-04-21 12:57:16 -07:00

418 lines
16 KiB
C++

//===--- SimplifySubstitutions.cpp - Simplify concrete type rules ---------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// Implements a pass for simplifying substitutions in concrete type symbols.
// Substitutions can be simplified in one of two ways; either a substitution
// term can be replaced by a more canonical term, or it can be replaced by a
// concrete type.
//
// For example, given pair of rewrite rules:
//
// T.[concrete: G<Y>] => T
// Y => X
//
// We can apply (Y => X) to the term appearing in the concrete type symbol
// [concrete: G<Y>] to obtain the rule:
//
// T.[concrete: G<X>] => T
//
// Similarly, if we have a pair of rewrite rules:
//
// T.[concrete: G<Y>] => T
// Y.[concrete: Int] => Y
//
// We can obtain the new rule:
//
// T.[concrete: G<Int>] => T
//
// Substitution simplification occurs during the Knuth-Bendix completion
// procedure, and after property map construction.
//
// In the first case, no property map is available yet, so substitution terms
// are simplified to other terms, but concrete type replacement is not
// performed. In the second case, the property map is consulted to perform
// concrete type replacement where appropriate.
//
// Either the new rule or the old rule can become redundant; they are related
// by rewrite loops. Additionally, rewrite loops are introduced for each
// transformation applied to the substitutions to relate them to the concrete
// type rules via "projections".
//
// These rewrite loops are in a sense dual to the property map's concrete type
// unification, and share a lot of the code; whereas the property map will
// relate two rules (T.[concrete: G<X>] => T) with (T.[concrete: G<Y>] => T)
// and add the induced rule (Y => X), substitution simplification will use
// (Y => X) to transform (T.[concrete: G<Y>] => T) into
// (T.[concrete: G<X>] => T).
//
// This logic (and concrete type unification) heavily relies on the "type
// difference" abstraction implemented in TypeDifference.cpp. Technical details
// about the various rewrite loops introduced here can be found in comments at
// the top of various functions below.
//
//===----------------------------------------------------------------------===//
#include "PropertyMap.h"
#include "RewriteSystem.h"
using namespace swift;
using namespace rewriting;
/// Given a rule (V.[LHS] => V) and a rewrite path (T.[RHS] => T) where
/// T == U.V, build a rewrite path from T.[RHS] to T.[LHS].
void RewriteSystem::buildRewritePathForUnifier(Term key,
unsigned lhsRuleID,
const RewritePath &rhsPath,
RewritePath *path) const {
unsigned lhsLength = getRule(lhsRuleID).getRHS().size();
unsigned lhsPrefix = key.size() - lhsLength;
path->append(rhsPath);
// Apply the inverted rule U.(V => V.[LHS]).
path->add(RewriteStep::forRewriteRule(
/*startOffset=*/lhsPrefix, /*endOffset=*/0,
/*ruleID=*/lhsRuleID, /*inverse=*/true));
// If the rule was actually (V.[LHS] => V) with T == U.V for some
// |U| > 0, prefix each substitution of [LHS] with U.
if (lhsPrefix > 0) {
path->add(RewriteStep::forPrefixSubstitutions(/*prefix=*/lhsPrefix,
/*endOffset=*/0,
/*inverse=*/false));
}
}
/// Build a rewrite path for a rule induced by concrete type unification.
///
/// Consider two concrete type rules (T.[LHS] => T) and (T.[RHS] => T), a
/// TypeDifference describing the transformation from LHS to RHS, and the
/// index of a substitution Xn from [C] which is transformed into its
/// replacement f(Xn).
///
/// The rewrite path should allow us to eliminate the induced rule
/// (f(Xn) => Xn), so the induced rule will appear without context, and
/// the concrete type rules (T.[LHS] => T) and (T.[RHS] => T) will appear
/// in context.
///
/// There are two cases:
///
/// a) The substitution Xn remains a type parameter in [RHS], but becomes
/// a canonical term Xn', so f(Xn) = Xn'.
///
/// In the first case, the induced rule (Xn => Xn'), described by a
/// rewrite path as follows:
///
/// Xn
/// Xn' T.[RHS] // RightConcreteProjection(n) pushes T.[RHS]
/// Xn' T // Application of (T.[RHS] => T) in context
/// Xn' T.[LHS] // Application of (T => T.[LHS]) in context
/// Xn' // LeftConcreteProjection(n) pops T.[LHS]
///
/// Now when this path is composed with a rewrite step for the inverted
/// induced rule (Xn' => Xn), we get a rewrite loop at Xn in which the
/// new rule appears in empty context.
///
/// b) The substitution Xn becomes a concrete type [D] in [C'], so
/// f(Xn) = Xn.[D].
///
/// In the second case, the induced rule is (Xn.[D] => Xn), described
/// by a rewrite path (going in the other direction) as follows:
///
/// Xn
/// Xn.[D] T.[RHS] // RightConcreteProjection(n) pushes T.[RHS]
/// Xn.[D] T // Application of (T.[RHS] => T) in context
/// Xn.[D] T.[LHS] // Application of (T => T.[LHS]) in context
/// Xn.[D] // LeftConcreteProjection(n) pops T.[LHS]
///
/// Now when this path is composed with a rewrite step for the induced
/// rule (Xn.[D] => Xn), we get a rewrite loop at Xn in which the
/// new rule appears in empty context.
///
/// There is a minor complication; the concrete type rules T.[LHS] and
/// T.[RHS] might actually be T.[LHS] and V.[RHS] where V is a suffix of
/// T, so T = U.V for some |U| > 0, (or vice versa). In this case we need
/// an additional step in the middle to prefix the concrete substitutions
/// of [LHS] (or [LHS]) with U.
static void buildRewritePathForInducedRule(Term key,
unsigned differenceID,
unsigned lhsRuleID,
const RewritePath &rhsPath,
unsigned substitutionIndex,
const RewriteSystem &system,
RewritePath *path) {
// Replace f(Xn) with Xn and push T.[RHS] on the stack.
path->add(RewriteStep::forRightConcreteProjection(
differenceID, substitutionIndex, /*inverse=*/false));
system.buildRewritePathForUnifier(key, lhsRuleID, rhsPath, path);
// Pop T.[LHS] from the stack, leaving behind Xn.
path->add(RewriteStep::forLeftConcreteProjection(
differenceID, substitutionIndex, /*inverse=*/true));
}
/// Given that LHS and RHS are known to simplify to the same term, build a
/// rewrite path from RHS to LHS.
void RewriteSystem::buildRewritePathForJoiningTerms(MutableTerm lhsTerm,
MutableTerm rhsTerm,
RewritePath *path) const {
(void) simplify(rhsTerm, path);
RewritePath lhsPath;
(void) simplify(lhsTerm, &lhsPath);
lhsPath.invert();
path->append(lhsPath);
assert(lhsTerm == rhsTerm);
}
/// Given two concrete type rules (T.[LHS] => T) and (T.[RHS] => T) and
/// TypeDifference describing the transformation from LHS to RHS,
/// record rules for transforming each substitution of LHS into a
/// more canonical type parameter or concrete type from RHS.
///
/// This also records rewrite paths relating induced rules to the original
/// concrete type rules, since the concrete type rules imply the induced
/// rules and make them redundant.
///
/// Finally, builds a rewrite loop relating the two concrete type rules
/// via the induced rules.
void RewriteSystem::processTypeDifference(const TypeDifference &difference,
unsigned differenceID,
unsigned lhsRuleID,
const RewritePath &rhsPath) {
if (!CheckedDifferences.insert(differenceID).second)
return;
bool debug = Debug.contains(DebugFlags::ConcreteUnification);
if (debug) {
difference.dump(llvm::dbgs());
}
RewritePath unificationPath;
auto substitutions = difference.LHS.getSubstitutions();
// The term is at the top of the primary stack. Push all substitutions onto
// the primary stack.
unificationPath.add(RewriteStep::forDecompose(substitutions.size(),
/*inverse=*/false));
// Move all substitutions but the first one to the secondary stack.
for (unsigned i = 1; i < substitutions.size(); ++i)
unificationPath.add(RewriteStep::forShift(/*inverse=*/false));
for (unsigned index : indices(substitutions)) {
// Move the next substitution from the secondary stack to the primary stack.
if (index != 0)
unificationPath.add(RewriteStep::forShift(/*inverse=*/true));
auto lhsTerm = difference.getReplacementSubstitution(index);
auto rhsTerm = difference.getOriginalSubstitution(index);
RewritePath inducedRulePath;
buildRewritePathForInducedRule(difference.BaseTerm, differenceID,
lhsRuleID, rhsPath, index,
*this, &inducedRulePath);
if (debug) {
llvm::dbgs() << "%% Induced rule " << lhsTerm
<< " => " << rhsTerm << " with path ";
inducedRulePath.dump(llvm::dbgs(), lhsTerm, *this);
llvm::dbgs() << "\n";
}
addRule(lhsTerm, rhsTerm, &inducedRulePath);
buildRewritePathForJoiningTerms(lhsTerm, rhsTerm, &unificationPath);
}
// All simplified substitutions are now on the primary stack. Collect them to
// produce the new term.
unificationPath.add(RewriteStep::forDecomposeConcrete(differenceID,
/*inverse=*/true));
// We now have a unification path from T.[RHS] to T.[LHS] using the
// newly-recorded induced rules. Close the loop with a path from
// T.[RHS] to R.[LHS] via the concrete type rules being unified.
buildRewritePathForUnifier(difference.BaseTerm, lhsRuleID, rhsPath,
&unificationPath);
// Record a rewrite loop at T.[LHS].
MutableTerm basepoint(difference.BaseTerm);
basepoint.add(difference.LHS);
recordRewriteLoop(basepoint, unificationPath);
// Optimization: If the LHS rule applies to the entire base term and not
// a suffix, mark it substitution-simplified so that we can skip recording
// the same rewrite loop in concretelySimplifyLeftHandSideSubstitutions().
auto &lhsRule = getRule(lhsRuleID);
if (lhsRule.getRHS() == difference.BaseTerm &&
!lhsRule.isSubstitutionSimplified()) {
if (lhsRule.isFrozen()) {
llvm::errs() << "Frozen rule should already be subst-simplified: "
<< lhsRule << "\n\n";
dump(llvm::errs());
abort();
}
lhsRule.markSubstitutionSimplified();
}
}
/// Simplify terms appearing in the substitutions of the last symbol of \p term,
/// which must be a superclass or concrete type symbol.
///
/// Additionally, if \p map is non-null, any terms which become concrete types
/// will cause the corresponding generic parameter in the concrete type symbol
/// to be replaced.
///
/// Returns None if the concrete type symbol cannot be simplified further.
///
/// Otherwise returns an index which can be passed to
/// RewriteSystem::getTypeDifference().
Optional<unsigned>
RewriteSystem::simplifySubstitutions(Term baseTerm, Symbol symbol,
const PropertyMap *map,
RewritePath *path) {
assert(symbol.hasSubstitutions());
// Fast path if the type is fully concrete.
auto substitutions = symbol.getSubstitutions();
if (substitutions.empty())
return None;
// Save the original rewrite path length so that we can reset if if we don't
// find anything to simplify.
unsigned oldSize = (path ? path->size() : 0);
if (path) {
// The term is at the top of the primary stack. Push all substitutions onto
// the primary stack.
path->add(RewriteStep::forDecompose(substitutions.size(),
/*inverse=*/false));
// Move all substitutions but the first one to the secondary stack.
for (unsigned i = 1; i < substitutions.size(); ++i)
path->add(RewriteStep::forShift(/*inverse=*/false));
}
// Simplify and collect substitutions.
llvm::SmallVector<std::pair<unsigned, Term>, 1> sameTypes;
llvm::SmallVector<std::pair<unsigned, Symbol>, 1> concreteTypes;
for (unsigned index : indices(substitutions)) {
// Move the next substitution from the secondary stack to the primary stack.
if (index != 0 && path)
path->add(RewriteStep::forShift(/*inverse=*/true));
auto term = symbol.getSubstitutions()[index];
MutableTerm mutTerm(term);
// Note that it's of course possible that the term both requires
// simplification, and the simplified term has a concrete type.
//
// This isn't handled with our current representation of
// TypeDifference, but that should be fine since the caller
// has to iterate until fixed point anyway.
//
// This should be rare in practice.
if (simplify(mutTerm, path)) {
// Record a mapping from this substitution to the simplified term.
sameTypes.emplace_back(index, Term::get(mutTerm, Context));
} else if (map) {
auto *props = map->lookUpProperties(mutTerm);
if (props && props->isConcreteType()) {
auto concreteSymbol = props->concretelySimplifySubstitution(
mutTerm, Context, path);
// Record a mapping from this substitution to the concrete type.
concreteTypes.emplace_back(index, concreteSymbol);
}
}
}
// If nothing changed, we don't have to build the type difference.
if (sameTypes.empty() && concreteTypes.empty()) {
if (path) {
// The rewrite path should consist of a Decompose, followed by a number
// of Shifts, followed by a Compose.
#ifndef NDEBUG
for (auto iter = path->begin() + oldSize; iter < path->end(); ++iter) {
assert(iter->Kind == RewriteStep::Shift ||
iter->Kind == RewriteStep::Decompose);
}
#endif
path->resize(oldSize);
}
return None;
}
auto difference = buildTypeDifference(baseTerm, symbol,
sameTypes, concreteTypes,
Context);
assert(difference.LHS != difference.RHS);
unsigned differenceID = recordTypeDifference(difference);
// All simplified substitutions are now on the primary stack. Collect them to
// produce the new term.
if (path) {
path->add(RewriteStep::forDecomposeConcrete(differenceID,
/*inverse=*/true));
}
return differenceID;
}
/// Simplify substitution terms in superclass, concrete type and concrete
/// conformance symbols.
///
/// During completion, \p map will be null. After completion, the property map
/// is built, and a final simplification pass is performed with \p map set to
/// the new property map.
void RewriteSystem::simplifyLeftHandSideSubstitutions(const PropertyMap *map) {
for (unsigned ruleID = FirstLocalRule, e = Rules.size(); ruleID < e; ++ruleID) {
auto &rule = getRule(ruleID);
if (rule.isSubstitutionSimplified())
continue;
auto optSymbol = rule.isPropertyRule();
if (!optSymbol || !optSymbol->hasSubstitutions())
continue;
auto symbol = *optSymbol;
auto differenceID = simplifySubstitutions(rule.getRHS(), symbol, map);
if (!differenceID)
continue;
auto difference = getTypeDifference(*differenceID);
assert(difference.LHS == symbol);
assert(difference.RHS != symbol);
MutableTerm rhs(rule.getRHS());
MutableTerm lhs(rhs);
lhs.add(difference.RHS);
addRule(lhs, rhs);
RewritePath path;
buildRewritePathForJoiningTerms(rhs, lhs, &path);
processTypeDifference(difference, *differenceID, ruleID, path);
}
}