diff --git a/demo/ListOperations.scala b/demo/ListOperations.scala index 3a08ac8f916661e89e2455eeaa2edf0593765f7e..086c05a22a427987c1bb0bb6934123f4f59c4392 100644 --- a/demo/ListOperations.scala +++ b/demo/ListOperations.scala @@ -75,6 +75,25 @@ object ListOperations { case Cons(x,xs) => Cons(x, append(xs, l2)) }) ensuring(content(_) == content(l1) ++ content(l2)) + def map(f : Map[Int,Int], l : List) : List = { + require(definedForAll(f, l)) + l match { + case Nil() => Nil() + case Cons(x, xs) => Cons(f(x), map(f, xs)) + } + } + + @induct + def appendMapCommute(l1 : List, l2 : List, f : Map[Int, Int]) : Boolean = { + require(definedForAll(f, l1) && definedForAll(f, l2)) + map(f, append(l1, l2)) == append(map(f, l1), map(f, l2)) + } holds + + def definedForAll(f : Map[Int, Int], l : List) : Boolean = l match { + case Nil() => true + case Cons(x, xs) => f.isDefinedAt(x) && definedForAll(f, xs) + } + @induct def nilAppend(l : List) : Boolean = (append(l, Nil()) == l) holds diff --git a/src/purescala/DefaultTactic.scala b/src/purescala/DefaultTactic.scala index c3e368be4ad432f1d2e291066263795402b6f7fe..7a212b667d54e2e42f1662c8a5044d26cb3ef34e 100644 --- a/src/purescala/DefaultTactic.scala +++ b/src/purescala/DefaultTactic.scala @@ -189,7 +189,7 @@ class DefaultTactic(reporter: Reporter) extends Tactic(reporter) { } // prec: there should be no lets and no pattern-matching in this expression - private def collectWithPathCondition(matcher: Expr=>Boolean, expression: Expr) : Set[(Seq[Expr],Expr)] = { + def collectWithPathCondition(matcher: Expr=>Boolean, expression: Expr) : Set[(Seq[Expr],Expr)] = { var collected : Set[(Seq[Expr],Expr)] = Set.empty def rec(expr: Expr, path: List[Expr]) : Unit = { diff --git a/src/purescala/FairZ3Solver.scala b/src/purescala/FairZ3Solver.scala index cbc3ae4f0b068fdbcc59791c574823ad19d1b793..0be2fcbfd2da21ac10d4eedc6818de0b9691fd57 100644 --- a/src/purescala/FairZ3Solver.scala +++ b/src/purescala/FairZ3Solver.scala @@ -666,6 +666,7 @@ class FairZ3Solver(val reporter: Reporter) extends Solver(reporter) with Abstrac //z3.assertCnstr(toZ3Formula(z3, And(newClauses)).get) blockingSet = blockingSet ++ Set(newBlockers.map(p => if(p._2) Not(Variable(p._1)) else Variable(p._1)) : _*) } + reporter.info(" - finished unrolling") unrollingStopwatch.stop } } diff --git a/src/purescala/InductionTactic.scala b/src/purescala/InductionTactic.scala index 32307c13c0c5de2e2fc20efa890c04547ac69c90..d93cc5957327f317dcf354dd8ea8413848d086e6 100644 --- a/src/purescala/InductionTactic.scala +++ b/src/purescala/InductionTactic.scala @@ -9,18 +9,6 @@ class InductionTactic(reporter: Reporter) extends DefaultTactic(reporter) { override val description = "Induction tactic for suitable functions" override val shortDescription = "induction" - private def singleAbsClassDef(args: VarDecls) : Option[AbstractClassDef] = { - val filtered = args.filter(arg => - arg.getType match { - case AbstractClassType(_) => true - case _ => false - }) - if (filtered.size != 1) None else (filtered.head.getType match { - case AbstractClassType(classDef) => Some(classDef) - case _ => scala.sys.error("This should not happen.") - }) - } - private def firstAbsClassDef(args: VarDecls) : Option[(AbstractClassDef, VarDecl)] = { val filtered = args.filter(arg => arg.getType match { @@ -86,4 +74,82 @@ class InductionTactic(reporter: Reporter) extends DefaultTactic(reporter) { defaultPost } } + + override def generatePreconditions(function: FunDef) : Seq[VerificationCondition] = { + val defaultPrec = super.generatePreconditions(function) + firstAbsClassDef(function.args) match { + case Some((classDef, arg)) => { + val toRet = if(function.hasBody) { + val cleanBody = expandLets(matchToIfThenElse(function.body.get)) + + val allPathConds = collectWithPathCondition((t => t match { + case FunctionInvocation(fd, _) if(fd.hasPrecondition) => true + case _ => false + }), cleanBody) + + def withPrec(path: Seq[Expr], shouldHold: Expr) : Expr = if(function.hasPrecondition) { + Not(And(And(matchToIfThenElse(function.precondition.get) +: path), Not(shouldHold))) + } else { + Not(And(And(path), Not(shouldHold))) + } + + val conditionsForAllPaths : Seq[Seq[VerificationCondition]] = allPathConds.map(pc => { + val path : Seq[Expr] = pc._1 + val fi = pc._2.asInstanceOf[FunctionInvocation] + val FunctionInvocation(fd, args) = fi + + val conditionsForEachChild = (for (child <- classDef.knownChildren) yield (child match { + case ccd @ CaseClassDef(id, prnt, vds) => { + val argAsVar = arg.toVariable + val selectors = selectorsOfParentType(classDefToClassType(classDef), ccd, argAsVar) + + val prec : Expr = freshenLocals(matchToIfThenElse(fd.precondition.get)) + val newLetIDs = fd.args.map(a => FreshIdentifier("arg_" + a.id.name, true).setType(a.tpe)) + val substMap = Map[Expr,Expr]((fd.args.map(_.toVariable) zip newLetIDs.map(Variable(_))) : _*) + val newBody : Expr = replace(substMap, prec) + val newCall : Expr = (newLetIDs zip args).foldRight(newBody)((iap, e) => Let(iap._1, iap._2, e)) + + val toProve = withPrec(path, newCall) + + val conditionForChild = + if (selectors.isEmpty) + toProve + else { + val inductiveHypothesis = (for (sel <- selectors) yield { + val prec : Expr = freshenLocals(matchToIfThenElse(fd.precondition.get)) + val newLetIDs = fd.args.map(a => FreshIdentifier("arg_" + a.id.name, true).setType(a.tpe)) + val substMap = Map[Expr,Expr]((fd.args.map(_.toVariable) zip newLetIDs.map(Variable(_))) : _*) + val newBody : Expr = replace(substMap, prec) + val newCall : Expr = (newLetIDs zip args).foldRight(newBody)((iap, e) => Let(iap._1, iap._2, e)) + + val toReplace = withPrec(path, newCall) + replace(Map(argAsVar -> sel), toReplace) + }) + Implies(And(inductiveHypothesis), toProve) + } + new VerificationCondition(Implies(CaseClassInstanceOf(ccd, argAsVar), conditionForChild), function, VCKind.Precondition, this).setPosInfo(fi) + } + case _ => scala.sys.error("Abstract class has non-case class subtype") + })) + println("Induction tactic yields the following VCs:") + println(conditionsForEachChild.map(vc => vc.condition).mkString("\n")) + conditionsForEachChild + }).toSeq + + conditionsForAllPaths.flatten + } else { + Seq.empty + } + + // println("PRECS VCs FOR " + function.id.name) + // println(toRet.toList.map(vc => vc.posInfo + " -- " + vc.condition).mkString("\n\n")) + + toRet + } + case None => { + reporter.warning("Induction tactic currently supports exactly one argument of abstract class type") + defaultPrec + } + } + } }