diff --git a/src/main/scala/leon/evaluators/Evaluator.scala b/src/main/scala/leon/evaluators/Evaluator.scala index c3d4c737f6d91492151b45c1acf99abf80574fda..9f7f0fbe2d85c44e28bbd160f03daa242d2c41a4 100644 --- a/src/main/scala/leon/evaluators/Evaluator.scala +++ b/src/main/scala/leon/evaluators/Evaluator.scala @@ -125,12 +125,29 @@ object Evaluator { */ private val checkCache: MutableMap[CaseClass, CheckStatus] = MutableMap.empty + /* Status of invariant checking + * + * For a given invariant, its checking status can be either + * - Complete(success) : checking has been performed previously and + * resulted in a value of `success`. + * - Pending : invariant is currently being checked somewhere + * in the program. If it fails, the failure is + * assumed to be bubbled up to all relevant failure + * points. + * - NoCheck : invariant has never been seen before. Discovering + * NoCheck for an invariant will automatically update + * the status to `Pending` as this creates a checking + * obligation. + */ sealed abstract class CheckStatus { + /* The invariant was invalid and this particular case class can't exist */ def isFailure: Boolean = this match { case Complete(status) => !status case _ => false } + /* The invariant has never been checked before and the checking obligation + * therefore falls onto the first caller of this method. */ def isRequired: Boolean = this == NoCheck } @@ -138,6 +155,7 @@ object Evaluator { case object Pending extends CheckStatus case object NoCheck extends CheckStatus + /* Status of the invariant checking for `cc` */ def invariantCheck(cc: CaseClass): CheckStatus = synchronized { if (!cc.ct.classDef.hasInvariant) Complete(true) else checkCache.get(cc).getOrElse { @@ -146,6 +164,7 @@ object Evaluator { } } + /* Update the cache with the invariant check result for `cc` */ def invariantResult(cc: CaseClass, success: Boolean): Unit = synchronized { checkCache(cc) = Complete(success) } diff --git a/src/main/scala/leon/purescala/Path.scala b/src/main/scala/leon/purescala/Path.scala index c7d6676766c7bb5c19cf582c4d9e8a44ad4729a0..c090d7010c86c728a668cc9769f7363826261267 100644 --- a/src/main/scala/leon/purescala/Path.scala +++ b/src/main/scala/leon/purescala/Path.scala @@ -20,7 +20,16 @@ object Path { def apply(path: Seq[Expr]): Path = new Path(path.map(Right(_))) } -/** Encodes path conditions */ +/** Encodes path conditions + * + * Paths are encoded as an (ordered) series of let-bindings and boolean + * propositions. A path is satisfiable iff all propositions are true + * in the context of the provided let-bindings. + * + * This encoding enables let-bindings over types for which equality is + * not defined, whereas previous encoding of let-bindings into equals + * could introduce non-sensical equations. + */ class Path private[purescala]( private[purescala] val elements: Seq[Either[(Identifier, Expr), Expr]]) { @@ -31,9 +40,22 @@ class Path private[purescala]( def --(ids: Set[Identifier]) = new Path(elements.filterNot(_.left.exists(p => ids(p._1)))) + /** Appends `that` path at the end of `this` */ def merge(that: Path) = new Path(elements ++ that.elements) + /** Transforms all expressions inside the path + * + * Expressions in a path appear both on the right-hand side of let binders + * and in boolean path conditions. + */ def map(f: Expr => Expr) = new Path(elements.map(_.left.map { case (id, e) => id -> f(e) }.right.map(f))) + + /** Splits the path on predicate `p` + * + * The path is split into + * 1. the sub-path containing all conditions that pass `p` (and all let-bindings), and + * 2. the sequence of conditions that didn't pass `p`. + */ def partition(p: Expr => Boolean): (Path, Seq[Expr]) = { val (passed, failed) = elements.partition { case Right(e) => p(e) @@ -43,16 +65,38 @@ class Path private[purescala]( (new Path(passed), failed.flatMap(_.right.toOption)) } + /** Check if the path is empty + * + * A path is empty iff it contains no let-bindings and its path condition is trivial. + */ def isEmpty = elements.filter { case Right(BooleanLiteral(true)) => false case _ => true }.isEmpty + /** Returns the negation of a path + * + * Path negation requires folding the path into a proposition and negating it. + * However, all external let-binders can be preserved in the resulting path, thus + * avoiding let-binding dupplication in future path foldings. + */ def negate: Path = { val (outers, rest) = elements.span(_.isLeft) new Path(outers :+ Right(not(fold[Expr](BooleanLiteral(true), let, Constructors.and(_, _))(rest)))) } + /** Returns a new path which depends ONLY on provided ids. + * + * Let-bindings obviously depend on some `id` if it corresponds to the bound + * identifier. An expression depends on an `id` iff the identifier is + * contained within the expression. + * + * This method makes little sense on its own and will typically be used from + * within a fixpoint computation where the `ids` set is iteratively computed + * by performing [[filterByIds]] calls on some (unchaning) base [[Path]]. + * + * @see [[leon.purescala.FunctionClosure.close]] for an example usecase. + */ def filterByIds(ids: Set[Identifier]): Path = { def containsIds(ids: Set[Identifier])(e: Expr): Boolean = exists{ case Variable(id) => ids.contains(id) @@ -66,6 +110,7 @@ class Path private[purescala]( new Path(newElements) } + /** Free variables within the path */ lazy val variables: Set[Identifier] = fold[Set[Identifier]](Set.empty, (id, e, res) => res - id ++ variablesOf(e), (e, res) => res ++ variablesOf(e) )(elements) @@ -75,20 +120,44 @@ class Path private[purescala]( def isBound(id: Identifier): Boolean = bindings.exists(p => p._1 == id) + /** Fold the path elements + * + * This function takes two combiner functions, one for let bindings and one + * for proposition expressions. + */ private def fold[T](base: T, combineLet: (Identifier, Expr, T) => T, combineCond: (Expr, T) => T) (elems: Seq[Either[(Identifier, Expr), Expr]]): T = elems.foldRight(base) { case (Left((id, e)), res) => combineLet(id, e, res) case (Right(e), res) => combineCond(e, res) } + /** Folds the path elements over a distributive proposition combinator [[combine]] + * + * Certain combiners can be distributive over let-binding folds. Namely, one + * requires that `combine(let a = b in e1, e2)` be equivalent to + * `let a = b in combine(e1, e2)` (where a \not\in FV(e2)). This is the case for + * - conjunction [[and]] + * - implication [[implies]] + */ private def distributiveClause(base: Expr, combine: (Expr, Expr) => Expr): Expr = { val (outers, rest) = elements.span(_.isLeft) val inner = fold[Expr](base, let, combine)(rest) fold[Expr](inner, let, (_,_) => scala.sys.error("Should never happen!"))(outers) } + /** Folds the path into a conjunct with the expression `base` */ def and(base: Expr) = distributiveClause(base, Constructors.and(_, _)) + + /** Fold the path into an implication of `base`, namely `path ==> base` */ def implies(base: Expr) = distributiveClause(base, Constructors.implies(_, _)) + + /** Folds the path into a `require` wrapping the expression `body` + * + * The function takes additional optional parameters + * - [[pre]] if one wishes to mix a pre-existing precondition into the final + * [[leon.purescala.Expressions.Require]], and + * - [[post]] for mixing a postcondition ([[leon.purescala.Expressions.Ensuring]]) in. + */ def specs(body: Expr, pre: Expr = BooleanLiteral(true), post: Expr = NoTree(BooleanType)) = { val (outers, rest) = elements.span(_.isLeft) val cond = fold[Expr](BooleanLiteral(true), let, Constructors.and(_, _))(rest) @@ -110,9 +179,18 @@ class Path private[purescala]( fold[Expr](full, let, (_, _) => scala.sys.error("Should never happen!"))(outers) } + /** Folds the path into the associated boolean proposition */ lazy val toClause: Expr = and(BooleanLiteral(true)) + + /** Like [[toClause]] but doesn't simplify final path through constructors + * from [[leon.purescala.Constructors]] */ lazy val fullClause: Expr = fold[Expr](BooleanLiteral(true), Let(_, _, _), And(_, _))(elements) + /** Folds the path into a boolean proposition where let-bindings are + * replaced by equations. + * + * CAUTION: Should only be used once INSIDE the solver!! + */ lazy val toPath: Expr = andJoin(elements.map { case Left((id, e)) => Equals(id.toVariable, e) case Right(e) => e