/// - `new_expression`: The updated [`Expression`] that replaces the original one after applying the rule.
/// - `new_top`: An additional top-level [`Vec<Expression>`] constraint that should be added to the model. If no top-level
/// - `symbols`: A [`SymbolTable`] containing any new symbol definitions or modifications to be added to the model's
/// - [`RuleEffect::new`]: Creates an effect with a new expression, top-level constraint, and symbol modifications.
/// - [`RuleEffect::pure`]: Creates an effect with only a new expression and no side-effects on the symbol table or constraints.
/// - [`RuleEffect::with_symbols`]: Creates an effect with a new expression and symbol table modifications, but no top-level constraint.
/// - [`RuleEffect::with_top`]: Creates an effect with a new expression and a top-level constraint, but no symbol table modifications.
/// - [`RuleEffect::sat`]: Creates an effect with a new expression, SAT encoding decisions and symbol modifications, but no top-level constraints.
/// The `apply` method allows for applying the changes represented by the `RuleEffect` to a [`Model`].
/// - [`ApplicationResult`]: Represents the result of applying a rule, which may either be a `RuleEffect` or an `ApplicationError`.
pub fn new(new_expression: Expression, new_top: Vec<Expression>, symbols: SymbolTable) -> Self {
/// top-level constraints, SAT encoding decisions, and speculative symbol table. Before returning, the symbol
/// A rule matches when any of its prefilter alternatives matches the focused expression. This keeps
/// compound filters such as `And / Comprehension` paired instead of forming a cross product with
/// symbol table changes avoids repeatedly scanning every declaration after unrelated expression
* - `rule_sets` A list of rule set names and priorities that this rule is a part of. This is used to populate rulesets at runtime.
pub rule_sets: &'a [(&'a str, u16)], // (name, priority). At runtime, we add the rule to rulesets