From d49d33dbab679524408b5fbd4a0939e03a172fee Mon Sep 17 00:00:00 2001
From: Nicolas Voirol <voirol.nicolas@gmail.com>
Date: Fri, 19 Jun 2015 13:21:11 +0200
Subject: [PATCH] Extended e-matching to generic matchers

---
 .../solvers/combinators/UnrollingSolver.scala |   4 +-
 .../templates/QuantificationManager.scala     | 501 +++++-------------
 .../leon/solvers/templates/Templates.scala    | 138 +++--
 .../solvers/templates/UnrollingBank.scala     |   4 +-
 .../scala/leon/solvers/z3/FairZ3Solver.scala  |   4 +-
 5 files changed, 244 insertions(+), 407 deletions(-)

diff --git a/src/main/scala/leon/solvers/combinators/UnrollingSolver.scala b/src/main/scala/leon/solvers/combinators/UnrollingSolver.scala
index 494f4b9b6..aa936b283 100644
--- a/src/main/scala/leon/solvers/combinators/UnrollingSolver.scala
+++ b/src/main/scala/leon/solvers/combinators/UnrollingSolver.scala
@@ -152,7 +152,7 @@ class UnrollingSolver(val context: LeonContext, program: Program, underlying: So
       reporter.debug(" - Running search...")
 
       solver.push()
-      solver.assertCnstr(andJoin((assumptions ++ unrollingBank.currentBlockers).toSeq))
+      solver.assertCnstr(andJoin((assumptions ++ unrollingBank.currentBlockers ++ unrollingBank.quantificationAssumptions).toSeq))
       val res = solver.check
 
       reporter.debug(" - Finished search with blocked literals")
@@ -188,7 +188,7 @@ class UnrollingSolver(val context: LeonContext, program: Program, underlying: So
             }
 
             solver.push()
-            solver.assertCnstr(andJoin(assumptions.toSeq))
+            solver.assertCnstr(andJoin(assumptions.toSeq ++ unrollingBank.quantificationAssumptions))
             val res2 = solver.check
 
             res2 match {
diff --git a/src/main/scala/leon/solvers/templates/QuantificationManager.scala b/src/main/scala/leon/solvers/templates/QuantificationManager.scala
index 2b76e02a9..ae69427dc 100644
--- a/src/main/scala/leon/solvers/templates/QuantificationManager.scala
+++ b/src/main/scala/leon/solvers/templates/QuantificationManager.scala
@@ -12,6 +12,10 @@ import Instantiation._
 
 import scala.collection.mutable.{Map => MutableMap, Set => MutableSet}
 
+case class Matcher[T](caller: T, tpe: TypeTree, args: Seq[T]) {
+  override def toString = "M(" + caller + " : " + tpe + ", " + args.mkString("(",",",")") + ")"
+}
+
 class QuantificationTemplate[T](
   val quantificationManager: QuantificationManager[T],
   val start: T,
@@ -24,6 +28,7 @@ class QuantificationTemplate[T](
   val clauses: Seq[T],
   val blockers: Map[T, Set[TemplateCallInfo[T]]],
   val applications: Map[T, Set[App[T]]],
+  val matchers: Map[T, Set[Matcher[T]]],
   val lambdas: Map[T, LambdaTemplate[T]]) {
 
   def instantiate(substMap: Map[T, T]): Instantiation[T] = {
@@ -50,13 +55,13 @@ object QuantificationTemplate {
     val holders: (Identifier, T) = holder -> encoder.encodeId(holder)
     val guards: (Identifier, T) = guard -> encoder.encodeId(guard)
 
-    val (clauses, blockers, applications, _) =
+    val (clauses, blockers, applications, matchers, _) =
       Template.encode(encoder, pathVar, quantifiers, condVars, exprVars, guardedExprs, lambdas,
         substMap = subst + holders + guards + qs)
 
     new QuantificationTemplate[T](quantificationManager,
       pathVar._2, qs, holders._2, guards._2, quantifiers,
-      condVars, exprVars, clauses, blockers, applications, lambdas)
+      condVars, exprVars, clauses, blockers, applications, matchers, lambdas)
   }
 }
 
@@ -93,7 +98,7 @@ class QuantificationManager[T](encoder: TemplateEncoder[T]) extends LambdaManage
     () => encoder.encodeId(id)
   }
 
-  private type Bindings = Set[(Option[T], FunctionType, Int, T)]
+  private type Bindings = Set[(Option[T], TypeTree, Int, T)]
   private var bindingsStack: List[Bindings] = List(Set.empty)
   private def bindings: Bindings = bindingsStack.head
   private def bindings_=(bs: Bindings): Unit = {
@@ -106,32 +111,72 @@ class QuantificationManager[T](encoder: TemplateEncoder[T]) extends LambdaManage
     quantificationsStack = qs :: quantificationsStack.tail
   }
 
-  private var instantiatedAppsStack: List[Seq[(T, App[T], Map[T,T])]] = List(Seq.empty)
-  private def instantiatedApps: Seq[(T, App[T], Map[T,T])] = instantiatedAppsStack.head
-  private def instantiatedApps_=(ias: Seq[(T, App[T], Map[T,T])]): Unit = {
-    instantiatedAppsStack = ias :: instantiatedAppsStack.tail
+  private var instantiatedMatchersStack: List[Seq[(T, Matcher[T], Map[T,T])]] = List(Seq.empty)
+  private def instantiatedMatchers: Seq[(T, Matcher[T], Map[T,T])] = instantiatedMatchersStack.head
+  private def instantiatedMatchers_=(ias: Seq[(T, Matcher[T], Map[T,T])]): Unit = {
+    instantiatedMatchersStack = ias :: instantiatedMatchersStack.tail
+  }
+
+  private var boundMatchersStack: List[Set[(T, Matcher[T])]] = List(Set.empty)
+  private def boundMatchers: Set[(T, Matcher[T])] = boundMatchersStack.head
+  private def boundMatchers_=(bas: Set[(T, Matcher[T])]): Unit = {
+    boundMatchersStack = bas :: boundMatchersStack.tail
+  }
+
+  private var knownStack: List[Set[T]] = List(Set.empty)
+  private def known(idT: T): Boolean = knownStack.head(idT) || byID.isDefinedAt(idT)
+
+  override def push(): Unit = {
+    bindingsStack = bindings :: bindingsStack
+    quantificationsStack = quantifications :: quantificationsStack
+    instantiatedMatchersStack = instantiatedMatchers :: instantiatedMatchersStack
+    boundMatchersStack = boundMatchers :: boundMatchersStack
+    knownStack = knownStack.head :: knownStack
+  }
+
+  override def pop(lvl: Int): Unit = {
+    bindingsStack = bindingsStack.drop(lvl)
+    quantificationsStack = quantificationsStack.drop(lvl)
+    instantiatedMatchersStack = instantiatedMatchersStack.drop(lvl)
+    boundMatchersStack = boundMatchersStack.drop(lvl)
+    knownStack = knownStack.drop(lvl)
   }
 
   def blockers: Seq[T] = quantifications.map(_.holdVar)
 
-  private def known(tpe: FunctionType, idT: T): Boolean = freeLambdas(tpe)(idT) || byID.isDefinedAt(idT)
+  override def registerFree(ids: Seq[(TypeTree, T)]): Unit = {
+    super.registerFree(ids)
+    knownStack = (knownStack.head ++ ids.map(_._2)) :: knownStack.tail
+  }
 
   private class Quantification (
-    var holdVar: T,
+    val qVar: T,
+    val holdVar: T,
     val guardVar: T,
+    var currentHolder: T,
     val condVars: Map[Identifier, T],
     val exprVars: Map[Identifier, T],
     val clauses: Seq[T],
     val blockers: Map[T, Set[TemplateCallInfo[T]]],
     val applications: Map[T, Set[App[T]]],
-    val lambdas: Map[T, LambdaTemplate[T]],
-    val binders: Set[App[T]]) {
+    val matchers: Set[Matcher[T]],
+    val lambdas: Map[T, LambdaTemplate[T]]) {
+
+    def this(
+      qVar: T, holdVar: T, guardVar: T,
+      condVars: Map[Identifier, T], exprVars: Map[Identifier, T],
+      clauses: Seq[T], blockers: Map[T, Set[TemplateCallInfo[T]]], applications: Map[T, Set[App[T]]],
+      matchers: Set[Matcher[T]], lambdas: Map[T, LambdaTemplate[T]]) = {
+        this(qVar, holdVar, guardVar, nextHolder(),
+          condVars, exprVars, clauses, blockers, applications, matchers, lambdas)
+    }
 
     def substitute(subst: Map[T, T]) = {
       val substituter = encoder.substitute(subst)
 
       new Quantification (
-        holdVar, guardVar, condVars, exprVars,
+        qVar, holdVar, guardVar, currentHolder,
+        condVars, exprVars,
         clauses map substituter,
         blockers map { case (b, fis) =>
           substituter(b) -> fis.map(fi => fi.copy(args = fi.args.map(substituter)))
@@ -139,64 +184,66 @@ class QuantificationManager[T](encoder: TemplateEncoder[T]) extends LambdaManage
         applications map { case (b, fas) =>
           substituter(b) -> fas.map(fa => fa.copy(caller = substituter(fa.caller), args = fa.args.map(substituter)))
         },
-        lambdas map { case (idT, template) => substituter(idT) -> template.substitute(subst) },
-        binders map { case app @ App(caller, _, args) =>
-          app.copy(caller = substituter(caller), args = args.map(substituter))
-        }
+        matchers map { case m @ Matcher(caller, _, args) =>
+          m.copy(caller = substituter(caller), args = args.map(substituter))
+        },
+        lambdas map { case (idT, template) => substituter(idT) -> template.substitute(subst) }
       )
     }
 
-    def instantiate(blocker: T, app: App[T], quantifierSubst: Map[T, T]): Instantiation[T] = {
-      val App(caller, tpe, args) = app
+    def instantiate(blocker: T, matcher: Matcher[T], quantifierSubst: Map[T, T]): Instantiation[T] = {
+      val Matcher(caller, tpe, args) = matcher
 
       // Build a mapping from applications in the quantified statement to all potential concrete
       // applications previously encountered. Also make sure the current `app` is in the mapping
       // as other instantiations have been performed previously when the associated applications
       // were first encountered.
-      val appMappings: List[List[(T, App[T], App[T])]] = binders.toList
+      val matcherMappings: Set[Set[(T, Matcher[T], Matcher[T])]] = matchers
         // 1. select an application in the quantified proposition for which the current app can
         //    be bound when generating the new constraints
-        .filter(qapp => qapp.caller == caller || (qapp.tpe == tpe && !known(qapp.tpe, qapp.caller)))
+        .filter(qm => qm.caller == caller || (qm.tpe == tpe && !known(qm.caller)))
         // 2. build the instantiation mapping associated to the chosen current application binding
-        .flatMap { bindingApp => binders
+        .flatMap { bindingMatcher => matchers
           // 2.1. select all potential matches for each quantified application
-          .map { case qapp @ App(qcaller, qtpe, qargs) =>
-            if (qapp == bindingApp) {
-              bindingApp -> Set(blocker -> app)
+          .map { case qm @ Matcher(qcaller, qtpe, qargs) =>
+            if (qm == bindingMatcher) {
+              bindingMatcher -> Set(blocker -> matcher)
             } else {
-              val instances: Seq[(T, App[T])] = instantiatedApps.collect {
-                case (b, app @ App(caller, tpe, _), _) if tpe == qtpe && (qcaller == caller || !known(qtpe, caller)) => (b, app)
+              val instances: Seq[(T, Matcher[T])] = instantiatedMatchers.collect {
+                case (b, m @ Matcher(caller, tpe, _), _) if tpe == qtpe && (qcaller == caller || !known(caller)) => (b, m)
               }
 
               // concrete applications can appear multiple times in the constraint, and this is also the case
               // for the current application for which we are generating the constraints
-              val withApp = if (qcaller == caller || !known(tpe, caller)) instances :+ (blocker -> app) else instances
+              val withCurrent = if (tpe == qtpe && (qcaller == caller || !known(caller))) {
+                instances :+ (blocker -> matcher)
+              } else instances
 
               // add quantified application to instances for constraints that depend on free variables
               // this also make sure that constraints that don't depend on all variables will also be instantiated
               // Note: we can use `blocker` here as the blocker since it is guaranteed true in this branch
-              val withAll = withApp :+ (blocker -> qapp)
+              val withAll = withCurrent :+ (blocker -> qm.copy(args = qm.args.map(a => quantifierSubst.getOrElse(a, a))))
 
-              qapp -> withAll
+              qm -> withAll
             }
           }
           // 2.2. based on the possible bindings for each quantified application, build a set of
           //      instantiation mappings that can be used to instantiate all necessary constraints
-          .foldLeft[List[List[(T, App[T], App[T])]]] (List(Nil)) {
-            case (mappings, (qapp, instances)) => instances.toList.flatMap {
-              case (b, app) => mappings.map(mapping => mapping :+ (b, qapp, app))
-            }
+          .foldLeft[Set[Set[(T, Matcher[T], Matcher[T])]]] (Set(Set.empty)) {
+            case (mappings, (qm, instances)) => Set(instances.toSeq.flatMap {
+              case (b, m) => mappings.map(mapping => mapping + ((b, qm, m)))
+            } : _*)
           }
         }
 
       var instantiation = Instantiation.empty[T]
 
-      for (mapping <- appMappings) {
+      for (mapping <- matcherMappings) {
         var constraints: List[T] = Nil
         var subst: Map[T, T] = Map.empty
 
         for {
-          (b, App(qcaller, _, qargs), App(caller, _, args)) <- mapping
+          (b, Matcher(qcaller, _, qargs), Matcher(caller, _, args)) <- mapping
           _ = constraints :+= b
           _ = if (qcaller != caller) constraints :+= encoder.mkEquals(qcaller, caller)
           (qarg, arg) <- (qargs zip args)
@@ -211,10 +258,10 @@ class QuantificationManager[T](encoder: TemplateEncoder[T]) extends LambdaManage
         }
 
         val enabler = if (constraints.size == 1) constraints.head else encoder.mkAnd(constraints : _*)
-        val holder = nextHolder()
+        val newHolder = nextHolder()
 
         val lambdaSubstMap = lambdas map { case (idT, lambda) => idT -> encoder.encodeId(lambda.id) }
-        val substMap = subst ++ lambdaSubstMap + (guardVar -> enabler) + (holdVar -> holder)
+        val substMap = subst ++ lambdaSubstMap + (qVar -> currentHolder) + (guardVar -> enabler) + (holdVar -> newHolder)
         val substituter = encoder.substitute(substMap)
 
         val newClauses = clauses map substituter
@@ -222,23 +269,20 @@ class QuantificationManager[T](encoder: TemplateEncoder[T]) extends LambdaManage
           substituter(b) -> fis.map(fi => fi.copy(args = fi.args.map(substituter)))
         }
 
-        val newApplications = applications map { case (b, fas) =>
-          substituter(b) -> fas.map(fa => fa.copy(caller = substituter(fa.caller), args = fa.args.map(substituter)))
-        }
-
         instantiation ++= (newClauses, newBlockers, Map.empty)
 
         for ((idT, lambda) <- lambdas) {
           val newIdT = substituter(idT)
           val newTemplate = lambda.substitute(substMap)
-          instantiation ++= QuantificationManager.super.instantiateLambda(newIdT, newTemplate)
+          instantiation ++= instantiateLambda(newIdT, newTemplate)
         }
 
-        for ((b, apps) <- newApplications; app <- apps) {
-          instantiation ++= QuantificationManager.super.instantiateApp(b, app)
+        for ((b, apps) <- applications; bp = substituter(b); app <- apps) {
+          val newApp = app.copy(caller = substituter(app.caller), args = app.args.map(substituter))
+          instantiation ++= instantiateApp(bp, newApp)
         }
 
-        holdVar = holder
+        currentHolder = newHolder
       }
 
       instantiation
@@ -247,67 +291,65 @@ class QuantificationManager[T](encoder: TemplateEncoder[T]) extends LambdaManage
 
   def instantiateQuantification(template: QuantificationTemplate[T], substMap: Map[T, T]): Instantiation[T] = {
 
-    val quantification: Quantification = {
+    val (quantification, matchers) = {
       val quantified = template.quantifiers.map(_._2).toSet
 
-      val bindingApps: Set[App[T]] = {
-        def rec(templates: Map[T, LambdaTemplate[T]]): Set[App[T]] = templates.flatMap {
-          case (_, template) => template.applications.flatMap(_._2).toSet ++ rec(template.lambdas)
+      val allMatchers: Set[Matcher[T]] = {
+        def rec(templates: Map[T, LambdaTemplate[T]]): Set[Matcher[T]] = templates.flatMap {
+          case (_, template) => template.matchers.flatMap(_._2).toSet ++ rec(template.lambdas)
         }.toSet
 
-        val allApps = template.applications.flatMap(_._2).toSet ++ rec(template.lambdas)
-        for (app @ App(caller, tpe, args) <- allApps if args exists quantified) yield app
+        val allMatchers = template.matchers.flatMap(_._2).toSet ++ rec(template.lambdas)
+        for (m @ Matcher(caller, tpe, args) <- allMatchers if args exists quantified) yield m
       }
 
       val q = new Quantification(
+        template.qs._2,
         template.holdVar,
         template.guardVar,
+        template.qs._2, // this Quantification will never be instantiated, so this increases sanity
         template.condVars,
         template.exprVars,
         template.clauses,
         template.blockers,
         template.applications,
-        template.lambdas,
-        bindingApps
+        allMatchers,
+        template.lambdas
       )
 
       val tpeCounts = template.quantifiers.groupBy(_._1.getType).mapValues(_.map(_._2).toSeq)
       val qSubst = tpeCounts.flatMap { case (tpe, idTs) => idTs zip standardQuantifiers(tpe, idTs.size) }.toMap
-      q.substitute(substMap ++ qSubst)
-    }
+      val subst = substMap ++ qSubst
 
-    val quantifierSubst: Map[T,T] = freshQuantifierSubst
+      val quantification = q.substitute(subst)
 
-    var qs: Seq[T] = Seq.empty
-    var instantiation: Instantiation[T] = {
-      val q = nextQ()
-      qs :+= q
-
-      val trueT = encoder.encodeExpr(Map.empty)(BooleanLiteral(true))
-      val base = quantification.substitute(Map(
-        quantification.guardVar -> trueT,
-        quantification.holdVar  -> trueT,
-        template.qs._2          -> q
-      ))
-
-      var instantiation: Instantiation[T] = (base.clauses, base.blockers, Map.empty)
-      for (q <- quantifications; (b, apps) <- quantification.applications; app <- apps) {
-        instantiation ++= q.instantiate(b, app, quantifierSubst)
+      val substituter = encoder.substitute(subst)
+      val matchers = template.matchers.map { case (b, ms) =>
+        substituter(b) -> ms.map(m => m.copy(caller = substituter(m.caller), args = m.args.map(substituter)))
       }
-      instantiation
+
+      (quantification, matchers)
     }
 
-    val qBindings: Bindings = quantification.binders.flatMap {
-      case App(caller, tpe, args) => args.zipWithIndex.collect {
+    val quantifierSubst: Map[T,T] = freshQuantifierSubst
+
+    var instantiation: Instantiation[T] = Template.instantiate(encoder, this,
+      quantification.clauses, quantification.blockers, quantification.applications,
+      Seq.empty, matchers, quantification.lambdas,
+      quantifierSubst + (quantification.guardVar -> encoder.encodeExpr(Map.empty)(BooleanLiteral(true)))
+    )
+
+    val qBindings: Bindings = quantification.matchers.flatMap {
+      case Matcher(caller, tpe, args) => args.zipWithIndex.collect {
         case (qid, idx) if quantified(qid) =>
-          (if (known(tpe, caller)) Some(caller) else None, tpe, idx, qid)
+          (if (known(caller)) Some(caller) else None, tpe, idx, qid)
       }
     }
 
     val (callerBindings, typeBindings) = (bindings ++ qBindings).partition(_._1.isDefined)
 
     val callerMap: Map[(T, Int), Set[T]] = callerBindings.groupBy(p => (p._1.get, p._3)).mapValues(_.map(_._4))
-    val typeMap: Map[(FunctionType, Int), Set[T]] = typeBindings.groupBy(p => (p._2, p._3)).mapValues(_.map(_._4))
+    val typeMap: Map[(TypeTree, Int), Set[T]] = typeBindings.groupBy(p => (p._2, p._3)).mapValues(_.map(_._4))
 
     val pairs: Set[(T, T)] = qBindings.flatMap { case (optIdT, tpe, idx, q) =>
       val matches = typeMap.getOrElse(tpe -> idx, Set.empty) ++ optIdT.toSeq.flatMap(idT => callerMap(idT -> idx))
@@ -320,9 +362,7 @@ class QuantificationManager[T](encoder: TemplateEncoder[T]) extends LambdaManage
       }
 
     val newQuantifications = for (mapping <- mappings) yield {
-      val ph = nextHolder()
-      val q = nextQ()
-      qs :+= q
+      val newQ = encoder.encodeId(FreshIdentifier("q", BooleanType))
 
       val freshConds = quantification.condVars.map(p => p._1.freshen -> p._2)
       val freshExprs = quantification.exprVars.map(p => p._1.freshen -> p._2)
@@ -330,11 +370,13 @@ class QuantificationManager[T](encoder: TemplateEncoder[T]) extends LambdaManage
       val substMap: Map[T, T] = mapping ++
         (freshConds ++ freshExprs).map { case (id, idT) => idT -> encoder.encodeId(id) } ++
         quantification.lambdas.map { case (idT, template) => idT -> encoder.encodeId(template.id) } +
-        (template.holdVar -> ph) + (template.qs._2 -> q)
+        (quantification.qVar -> newQ)
 
       val substituter = encoder.substitute(substMap)
 
-      new Quantification(ph, quantification.guardVar,
+      new Quantification(newQ,
+        quantification.holdVar, quantification.guardVar,
+        newQ, // set the fresh qVar as `currentHolder` to ensure blocking before first unfolding
         freshConds mapValues substituter,
         freshExprs mapValues substituter,
         quantification.clauses map substituter,
@@ -344,21 +386,24 @@ class QuantificationManager[T](encoder: TemplateEncoder[T]) extends LambdaManage
         quantification.applications map { case (b, fas) =>
           substituter(b) -> fas.map(fa => fa.copy(caller = substituter(fa.caller), args = fa.args.map(substituter)))
         },
-        quantification.lambdas map { case (idT, template) => substituter(idT) -> template.substitute(mapping) },
-        quantification.binders map { case app @ App(caller, _, args) =>
-          app.copy(caller = substituter(caller), args = args.map(substituter))
-        }
+        quantification.matchers map { case m @ Matcher(caller, _, args) =>
+          m.copy(caller = substituter(caller), args = args.map(substituter))
+        },
+        quantification.lambdas map { case (idT, template) => substituter(idT) -> template.substitute(mapping) }
       )
     }
 
     val eqClause = {
+      val qs = newQuantifications.map(_.qVar)
       val newQs = if (qs.size > 1) encoder.mkAnd(qs : _*) else qs.head
-      encoder.mkImplies(substMap(template.start), encoder.mkEquals(template.qs._2, newQs))
+      encoder.mkImplies(substMap(template.start), encoder.mkEquals(template.holdVar, newQs))
     }
 
     instantiation = (instantiation._1 :+ eqClause, instantiation._2, instantiation._3)
 
-    instantiatedApps = for ((b, app, qSubst) <- instantiatedApps) yield {
+    // ensure the `quantifierSubst` associated to each instantiated app covers all necessary
+    // parameters for the current new quantifications
+    instantiatedMatchers = for ((b, m, qSubst) <- instantiatedMatchers) yield {
       val nqSubst = if (qSubst.size == stdQuantifiers.map(_._2.size).sum) qSubst else {
         stdQuantifiers.flatMap { case (tpe, ids) =>
           val recent = ids.dropWhile(qSubst.isDefinedAt)
@@ -366,286 +411,34 @@ class QuantificationManager[T](encoder: TemplateEncoder[T]) extends LambdaManage
         }.toMap
       }
 
-      for (q <- newQuantifications) {
-        instantiation ++= q.instantiate(b, app, nqSubst)
-      }
-
-      (b, app, nqSubst)
-    }
-
-    for ((b, apps) <- quantification.applications; app <- apps) {
-      instantiatedApps :+= (b, app, quantifierSubst)
+      (b, m, nqSubst)
     }
 
-    instantiation
-  }
+    quantifications ++= newQuantifications
 
-  override def instantiateApp(blocker: T, app: App[T]): Instantiation[T] = {
-    val quantifierSubst: Map[T, T] = freshQuantifierSubst
-
-    var instantiation = Instantiation.empty[T]
-    for (q <- quantifications) {
-      instantiation ++= q.instantiate(blocker, app, quantifierSubst)
+    for ((b, m, qSubst) <- instantiatedMatchers; q <- newQuantifications) {
+      instantiation ++= q.instantiate(b, m, qSubst)
     }
 
-    instantiatedApps :+= (blocker, app, quantifierSubst)
-
-    instantiation ++ super.instantiateApp(blocker, app)
-  }
-
-  /*
-  private class QuantificationScope(
-    val quantifiedApps: List[App[T]],
-    val quantifiers: Map[Identifier, T],
-    val condVars: Map[Identifier, T],
-    val exprVars: Map[Identifier, T],
-    val clauses: Seq[T],
-    val blockers: Map[T, Set[TemplateCallInfo[T]]],
-    val applications: Map[T, Set[App[T]]],
-    val lambdas: Map[T, LambdaTemplate[T]],
-    val res: Set[T]
-  ) {
-    def this() = this(Nil, Map.empty, Map.empty, Map.empty, Seq.empty, Map.empty, Map.empty, Map.empty, Set.empty)
-    def this(scope: QuantificationScope) = this(
-      scope.quantifiedApps,
-      scope.quantifiers,
-      scope.condVars,
-      scope.exprVars,
-      scope.clauses,
-      scope.blockers,
-      scope.applications,
-      scope.lambdas,
-      scope.res
-    )
-
-    lazy val quantified: Set[T] = quantifiers.values.toSet
-    def free(tpe: FunctionType, idT: T): Boolean = res(idT) || freeLambdas(tpe)(idT)
+    instantiation
   }
 
-  private var scopes: List[QuantificationScope] = List(new QuantificationScope)
-  private def scope: QuantificationScope = scopes.head
-
-  override def push(): Unit = {
-    self.push()
-
-    val currentScope = scope
-    scopes = new QuantificationScope(currentScope) :: scopes.tail
-  }
+  def instantiateMatcher(blocker: T, matcher: Matcher[T]): Instantiation[T] = {
+    val qInst = if (boundMatchers(blocker -> matcher)) Instantiation.empty[T] else {
+      val quantifierSubst: Map[T, T] = freshQuantifierSubst
 
-  override def pop(lvl: Int): Unit = {
-    self.pop(lvl)
-    scopes = scopes.drop(lvl)
-  }
-
-  def registerQuantified(
-    startVar: Identifier,
-    quantifierVars: Seq[Identifier],
-    condVars: Map[Identifier, T],
-    exprVars: Map[Identifier, T],
-    guardedExprs: Map[Identifier, Seq[Expr]],
-    lambdas: Map[T, LambdaTemplate[T]],
-    subst: Map[Identifier, T],
-    res: Option[T]
-  ): Unit = {
-    val currentScope = scope
-    def free(tpe: FunctionType, idT: T): Boolean = currentScope.free(tpe, idT) || res.exists(_ == idT)
-
-    val quantifiers: Seq[(Identifier, T)] = quantifierVars.map(id => id -> encoder.encodeId(id))
-    val quantified: Set[T] = quantifiers.map(_._2).toSet
-    val pathVar = startVar -> subst(startVar)
-    val (clauses, blockers, apps, _) = Template.encode(encoder, pathVar, quantifiers, condVars, exprVars, guardedExprs, lambdas, subst)
-
-    val bindingApps: Set[App[T]] = {
-      def rec(templates: Map[T, LambdaTemplate[T]]): Set[App[T]] = templates.flatMap {
-        case (_, template) => template.applications.flatMap(_._2).toSet ++ rec(template.lambdas)
-      }.toSet
-
-      val allApps = apps.flatMap(_._2).toSet
-      for (app @ App(caller, tpe, args) <- (allApps ++ rec(lambdas)) if args exists quantified) yield app
-    }
-
-    val argumentBindings: Set[(Option[T], FunctionType, Int, T)] = bindingApps.flatMap {
-      case App(caller, tpe, args) => args.zipWithIndex.collect {
-        case (qid, idx) if quantified(qid) => (if (free(tpe, caller)) Some(caller) else None, tpe, idx, qid)
-      }
-    }
-
-    val (callerBindings, typeBindings) = argumentBindings.partition(_._1.isDefined)
-
-    val typeMap: Map[FunctionType, Set[(Int, T)]] = typeBindings.groupBy(_._2).mapValues(_.map(p => (p._3, p._4)))
-    val typePairs: Seq[(T, T)] = typeMap.toSeq.flatMap {
-      case (_, argBinds) => argBinds.groupBy(_._1).mapValues(_.map(_._2)).toSeq.flatMap {
-        case (_, options) => options.flatMap(o1 => options.map(o2 => o1 -> o2))
-      }
-    }
-
-    val callerPairs: Seq[(T, T)] = callerBindings.groupBy(p => (p._1.get, p._2)).toSeq.flatMap {
-      case ((freeFunction, tpe), arguments) =>
-        val callerIdentified: Set[(Int, T)] = arguments.map(p => (p._3, p._4))
-        val typeIdentified: Set[(Int, T)] = typeMap.getOrElse(tpe, Set.empty)
-
-        (callerIdentified ++ typeIdentified).groupBy(_._1).mapValues(_.map(_._2)).toSeq.flatMap {
-          case (_, options) => options.flatMap(o1 => options.map(o2 => o1 -> o2))
-        }
-    }
-
-    val filteredPairs: Set[(T, T)] = {
-      def rec(
-        mappings: Seq[(T, T)],
-        result: Set[(T, T)]
-      ): Set[(T, T)] = mappings match {
-        case (p @ (x, y)) +: tail if !result(p) && !result(y -> x) => rec(tail, result + p)
-        case p +: tail => rec(tail, result)
-        case Seq() => result
-      }
-
-      rec(callerPairs ++ typePairs, Set.empty)
-    }
-
-    val mappings: List[Map[T, T]] =
-      filteredPairs.groupBy(_._1).toSeq.foldLeft(List(Map.empty[T, T])) {
-        case (mappings, (_, pairs)) => pairs.toList.flatMap(p => mappings.map(mapping => mapping + p))
+      var instantiation = Instantiation.empty[T]
+      for (q <- quantifications) {
+        instantiation ++= q.instantiate(blocker, matcher, quantifierSubst)
       }
 
-    val (allClauses, allBlockers, allApps, quantifiedApps) = {
-      var allClauses  : Seq[T]                           = Seq.empty
-      var allBlockers : Map[T, Set[TemplateCallInfo[T]]] = Map.empty.withDefaultValue(Set.empty)
-      var allApps     : Map[T, Set[App[T]]]              = Map.empty.withDefaultValue(Set.empty)
-
-      var quantifiedApps : Set[App[T]] = Set.empty
-
-      for (mapping <- mappings) {
-        val substituter = encoder.substitute(mapping)
-        allClauses ++= clauses.map(substituter)
-
-        blockers.foreach { case (b, fis) =>
-          val bp = substituter(b)
-          val fisp = fis.map(fi => fi.copy(args = fi.args.map(substituter)))
-          allBlockers += bp -> (allBlockers(bp) ++ fisp)
-        }
+      instantiatedMatchers :+= (blocker, matcher, quantifierSubst)
+      boundMatchers += (blocker -> matcher)
 
-        apps.foreach { case (b, fas) =>
-          val bp = substituter(b)
-          val fasp = fas.map(fa => fa.copy(caller = substituter(fa.caller), args = fa.args.map(substituter)))
-          allApps += bp -> (allApps(bp) ++ fasp)
-        }
-
-        quantifiedApps ++= bindingApps.map { case app @ App(caller, _, args) =>
-          app.copy(caller = substituter(caller), args = args.map(substituter))
-        }
-      }
-
-      (allClauses, allBlockers, allApps, quantifiedApps)
+      instantiation
     }
 
-    scopes = new QuantificationScope(
-      currentScope.quantifiedApps ++ quantifiedApps,
-      currentScope.quantifiers ++ quantifiers,
-      currentScope.condVars ++ condVars,
-      currentScope.exprVars ++ exprVars,
-      currentScope.clauses ++ allClauses,
-      currentScope.blockers merge allBlockers,
-      currentScope.applications merge allApps,
-      currentScope.lambdas ++ lambdas,
-      currentScope.res ++ res
-    ) :: scopes.tail
+    qInst
   }
 
-  override def instantiateApp(blocker: T, app: App[T]): Instantiation[T] = {
-    val App(caller, tpe, args) = app
-    val currentScope = scope
-    import currentScope._
-
-    // Build a mapping from applications in the quantified statement to all potential concrete
-    // applications previously encountered. Also make sure the current `app` is in the mapping
-    // as other instantiations have been performed previously when the associated applications
-    // were first encountered.
-    val appMappings: List[List[(App[T], App[T])]] = quantifiedApps
-      // 1. select an application in the quantified proposition for which the current app can
-      //    be bound when generating the new constraints
-      .filter(qapp => qapp.caller == caller || (qapp.tpe == tpe && !free(qapp.tpe, qapp.caller)))
-      // 2. build the instantiation mapping associated to the chosen current application binding
-      .flatMap { bindingApp => quantifiedApps
-        // 2.1. select all potential matches for each quantified application
-        .map { case qapp @ App(qcaller, qtpe, qargs) =>
-          if (qapp == bindingApp) {
-            bindingApp -> Set(app)
-          } else {
-            val instances = self.applications(qtpe).collect {
-              case (_, app @ App(caller, _, _)) if qcaller == caller || !free(qtpe, caller) => app
-            }
-
-            // concrete applications can appear multiple times in the constraint, and this is also the case
-            // for the current application for which we are generating the constraints
-            val withApp = if (qcaller == caller || !free(tpe, caller)) instances + app else instances
-
-            // add quantified application to instances for constraints that depend on free variables
-            // this also make sure that constraints that don't depend on all variables will also be instantiated
-            val withAll = withApp + qapp
-
-            qapp -> withAll
-          }
-        }
-        // 2.2. based on the possible bindings for each quantified application, build a set of
-        //      instantiation mappings that can be used to instantiate all necessary constraints
-        .foldLeft[List[List[(App[T], App[T])]]] (List(Nil)) {
-          case (mappings, (qapp, instances)) =>
-            instances.toList.flatMap(app => mappings.map(mapping => mapping :+ (app -> qapp)))
-        }
-      }
-
-    var instantiation = Instantiation.empty[T]
-
-    for (mapping <- appMappings) {
-      var constraints: List[T] = Nil
-      var subst: Map[T, T] = Map.empty
-
-      for {
-        (App(qcaller, _, qargs), App(caller, _, args)) <- mapping
-        _ = if (qcaller != caller) constraints :+= encoder.mkEquals(qcaller, caller)
-        (qarg, arg) <- (qargs zip args)
-      } if (subst.isDefinedAt(qarg) || !quantified(qarg)) {
-        constraints :+= encoder.mkEquals(qarg, arg)
-      } else {
-        subst += qarg -> arg
-      }
-
-      // make sure we freshen quantified variables that haven't been bound by concrete calls
-      subst ++= quantifiers.collect {
-        case (id, idT) if !subst.isDefinedAt(idT) => idT -> encoder.encodeId(id)
-      }
-
-      val bp = encoder.encodeId(FreshIdentifier("qb", BooleanType))
-      // TODO: empty `constraints`
-      val enabler = encoder.mkEquals(bp, encoder.mkAnd(constraints : _*))
-
-      val lambdaSubstMap = lambdas.map { case (idT, lambda) => idT -> encoder.encodeId(lambda.id) }
-      val substMap = subst ++ lambdaSubstMap + (start -> enabler)
-      val substituter = encoder.substitute(subst)
-
-      val newClauses = enabler +: clauses.map(substituter)
-      val newBlockers = blockers.map { case (b, fis) =>
-        substituter(b) -> fis.map(fi => fi.copy(args = fi.args.map(substituter)))
-      }
-
-      val newApplications = currentScope.applications.map { case (b, fas) =>
-        substituter(b) -> fas.map(fa => fa.copy(caller = substituter(fa.caller), args = fa.args.map(substituter)))
-      }
-
-      instantiation ++= (newClauses, newBlockers, Map.empty)
-
-      for ((idT, lambda) <- lambdas) {
-        val newIdT = substituter(idT)
-        val newTemplate = lambda.substitute(substMap)
-        instantiation ++= instantiateLambda(newIdT, newTemplate)
-      }
-
-      for ((b, apps) <- newApplications; app <- apps) {
-        instantiation ++= instantiateApp(b, app)
-      }
-    }
-
-    instantiation
-  }
-  */
 }
diff --git a/src/main/scala/leon/solvers/templates/Templates.scala b/src/main/scala/leon/solvers/templates/Templates.scala
index 91c8ca699..ab63924ef 100644
--- a/src/main/scala/leon/solvers/templates/Templates.scala
+++ b/src/main/scala/leon/solvers/templates/Templates.scala
@@ -12,9 +12,7 @@ import purescala.Types._
 import purescala.Definitions._
 
 case class App[T](caller: T, tpe: FunctionType, args: Seq[T]) {
-  override def toString = {
-    "(" + caller + " : " + tpe + ")" + args.mkString("(", ",", ")")
-  }
+  override def toString = "(" + caller + " : " + tpe + ")" + args.mkString("(", ",", ")")
 }
 
 object Instantiation {
@@ -45,7 +43,7 @@ import Instantiation.{empty => _, _}
 
 trait Template[T] { self =>
   val encoder : TemplateEncoder[T]
-  val lambdaManager : LambdaManager[T]
+  val manager : QuantificationManager[T]
 
   val start : T
   val args : Seq[T]
@@ -55,6 +53,7 @@ trait Template[T] { self =>
   val blockers : Map[T, Set[TemplateCallInfo[T]]]
   val applications : Map[T, Set[App[T]]]
   val quantifications: Seq[QuantificationTemplate[T]]
+  val matchers: Map[T, Set[Matcher[T]]]
   val lambdas : Map[T, LambdaTemplate[T]]
 
   private var substCache : Map[Seq[T],Map[T,T]] = Map.empty
@@ -71,36 +70,10 @@ trait Template[T] { self =>
     }
 
     val lambdaSubstMap = lambdas.map { case (idT, lambda) => idT -> encoder.encodeId(lambda.id) }
-
     val substMap : Map[T,T] = baseSubstMap ++ lambdaSubstMap + (start -> aVar)
-    val substituter : T => T = encoder.substitute(substMap)
-
-    val newClauses = clauses.map(substituter)
-    val newBlockers = blockers.map { case (b,fis) =>
-      substituter(b) -> fis.map(fi => fi.copy(args = fi.args.map(substituter)))
-    }
-
-    val newApplications = applications.map { case (b,fas) =>
-      substituter(b) -> fas.map(fa => fa.copy(caller = substituter(fa.caller), args = fa.args.map(substituter)))
-    }
-
-    var instantiation: Instantiation[T] = (newClauses, newBlockers, Map.empty)
-
-    for ((idT, lambda) <- lambdas) {
-      val newIdT = substituter(idT)
-      val newTemplate = lambda.substitute(substMap)
-      instantiation ++= lambdaManager.instantiateLambda(newIdT, newTemplate)
-    }
-
-    for ((b,apps) <- newApplications; app <- apps) {
-      instantiation ++= lambdaManager.instantiateApp(b, app)
-    }
-
-    for (q <- quantifications) {
-      instantiation ++= q.instantiate(substMap)
-    }
 
-    instantiation
+    Template.instantiate(encoder, manager,
+      clauses, blockers, applications, quantifications, matchers, lambdas, substMap)
   }
 
   override def toString : String = "Instantiated template"
@@ -150,6 +123,11 @@ object Template {
     (templates, apps)
   }
 
+  private def matchersOf[T](encodeExpr: Expr => T)(expr: Expr): Set[Matcher[T]] = collect[Matcher[T]] {
+    case Application(caller, args) => Set(Matcher(encodeExpr(caller), caller.getType, args.map(encodeExpr)))
+    case _ => Set.empty
+  }(expr)
+
   def encode[T](
     encoder: TemplateEncoder[T],
     pathVar: (Identifier, T),
@@ -161,7 +139,7 @@ object Template {
     substMap: Map[Identifier, T] = Map.empty[Identifier, T],
     optCall: Option[TypedFunDef] = None,
     optApp: Option[(T, FunctionType)] = None
-  ) : (Seq[T], Map[T, Set[TemplateCallInfo[T]]], Map[T, Set[App[T]]], () => String) = {
+  ) : (Seq[T], Map[T, Set[TemplateCallInfo[T]]], Map[T, Set[App[T]]], Map[T, Set[Matcher[T]]], () => String) = {
 
     val idToTrId : Map[Identifier, T] = {
       condVars ++ exprVars + pathVar ++ arguments ++ substMap ++
@@ -175,21 +153,27 @@ object Template {
     }).toSeq
 
     val extractInfos : Expr => (Set[TemplateCallInfo[T]], Set[App[T]]) = functionCallInfos(encodeExpr)
+    val extractMatchers: Expr => Set[Matcher[T]] = matchersOf(encodeExpr)
+
     val optIdCall = optCall.map(tfd => TemplateCallInfo[T](tfd, arguments.map(_._2)))
     val optIdApp = optApp.map { case (idT, tpe) => App(idT, tpe, arguments.map(_._2)) }
+    val optIdMatch = optIdApp.map { case App(caller, tpe, args) => Matcher(caller, tpe, args) }
 
-    val (blockers, applications) : (Map[Identifier, Set[TemplateCallInfo[T]]], Map[Identifier, Set[App[T]]]) = {
+    val (blockers, applications, matchers) = {
       var blockers : Map[Identifier, Set[TemplateCallInfo[T]]] = Map.empty
       var applications : Map[Identifier, Set[App[T]]] = Map.empty
+      var matchers : Map[Identifier, Set[Matcher[T]]] = Map.empty
 
       for ((b,es) <- guardedExprs) {
-        var funInfos : Set[TemplateCallInfo[T]] = Set.empty
-        var appInfos : Set[App[T]] = Set.empty
+        var funInfos   : Set[TemplateCallInfo[T]] = Set.empty
+        var appInfos   : Set[App[T]]              = Set.empty
+        var matchInfos : Set[Matcher[T]]          = Set.empty
 
         for (e <- es) {
           val (newFunInfos, newAppInfos) = extractInfos(e)
           funInfos ++= newFunInfos
           appInfos ++= newAppInfos
+          matchInfos ++= extractMatchers(e)
         }
 
         val calls = funInfos -- optIdCall
@@ -197,13 +181,17 @@ object Template {
 
         val apps = appInfos -- optIdApp
         if (apps.nonEmpty) applications += b -> apps
+
+        val matchs = matchInfos -- optIdMatch
+        if (matchs.nonEmpty) matchers += b -> matchs
       }
 
-      (blockers, applications)
+      (blockers, applications, matchers)
     }
 
     val encodedBlockers : Map[T, Set[TemplateCallInfo[T]]] = blockers.map(p => idToTrId(p._1) -> p._2)
     val encodedApps : Map[T, Set[App[T]]] = applications.map(p => idToTrId(p._1) -> p._2)
+    val encodedMatchers : Map[T, Set[Matcher[T]]] = matchers.map(p => idToTrId(p._1) -> p._2)
 
     val stringRepr : () => String = () => {
       " * Activating boolean : " + pathVar._1 + "\n" +
@@ -222,7 +210,51 @@ object Template {
       }.mkString("\n")
     }
 
-    (clauses, encodedBlockers, encodedApps, stringRepr)
+    (clauses, encodedBlockers, encodedApps, encodedMatchers, stringRepr)
+  }
+
+  def instantiate[T](
+    encoder: TemplateEncoder[T],
+    manager: QuantificationManager[T],
+    clauses: Seq[T],
+    blockers: Map[T, Set[TemplateCallInfo[T]]],
+    applications: Map[T, Set[App[T]]],
+    quantifications: Seq[QuantificationTemplate[T]],
+    matchers: Map[T, Set[Matcher[T]]],
+    lambdas: Map[T, LambdaTemplate[T]],
+    substMap: Map[T, T]
+  ): Instantiation[T] = {
+
+    val substituter : T => T = encoder.substitute(substMap)
+
+    val newClauses = clauses.map(substituter)
+    val newBlockers = blockers.map { case (b,fis) =>
+      substituter(b) -> fis.map(fi => fi.copy(args = fi.args.map(substituter)))
+    }
+
+    var instantiation: Instantiation[T] = (newClauses, newBlockers, Map.empty)
+
+    for ((idT, lambda) <- lambdas) {
+      val newIdT = substituter(idT)
+      val newTemplate = lambda.substitute(substMap)
+      instantiation ++= manager.instantiateLambda(newIdT, newTemplate)
+    }
+
+    for ((b,apps) <- applications; bp = substituter(b); app <- apps) {
+      val newApp = app.copy(caller = substituter(app.caller), args = app.args.map(substituter))
+      instantiation ++= manager.instantiateApp(bp, newApp)
+    }
+
+    for ((b, matchs) <- matchers; bp = substituter(b); m <- matchs) {
+      val newM = m.copy(caller = substituter(m.caller), args = m.args.map(substituter))
+      instantiation ++= manager.instantiateMatcher(bp, newM)
+    }
+
+    for (q <- quantifications) {
+      instantiation ++= q.instantiate(substMap)
+    }
+
+    instantiation
   }
 }
 
@@ -231,7 +263,7 @@ object FunctionTemplate {
   def apply[T](
     tfd: TypedFunDef,
     encoder: TemplateEncoder[T],
-    lambdaManager: LambdaManager[T],
+    manager: QuantificationManager[T],
     pathVar: (Identifier, T),
     arguments: Seq[(Identifier, T)],
     condVars: Map[Identifier, T],
@@ -242,7 +274,7 @@ object FunctionTemplate {
     isRealFunDef: Boolean
   ) : FunctionTemplate[T] = {
     
-    val (clauses, blockers, applications, templateString) =
+    val (clauses, blockers, applications, matchers, templateString) =
       Template.encode(encoder, pathVar, arguments, condVars, exprVars, guardedExprs, lambdas,
         substMap = quantifications.map(q => q.qs).toMap,
         optCall = Some(tfd))
@@ -256,7 +288,7 @@ object FunctionTemplate {
     new FunctionTemplate[T](
       tfd,
       encoder,
-      lambdaManager,
+      manager,
       pathVar._2,
       arguments.map(_._2),
       condVars,
@@ -265,6 +297,7 @@ object FunctionTemplate {
       blockers,
       applications,
       quantifications,
+      matchers,
       lambdas,
       isRealFunDef,
       funString
@@ -275,7 +308,7 @@ object FunctionTemplate {
 class FunctionTemplate[T] private(
   val tfd: TypedFunDef,
   val encoder: TemplateEncoder[T],
-  val lambdaManager: LambdaManager[T],
+  val manager: QuantificationManager[T],
   val start: T,
   val args: Seq[T],
   val condVars: Map[Identifier, T],
@@ -284,6 +317,7 @@ class FunctionTemplate[T] private(
   val blockers: Map[T, Set[TemplateCallInfo[T]]],
   val applications: Map[T, Set[App[T]]],
   val quantifications: Seq[QuantificationTemplate[T]],
+  val matchers: Map[T, Set[Matcher[T]]],
   val lambdas: Map[T, LambdaTemplate[T]],
   isRealFunDef: Boolean,
   stringRepr: () => String) extends Template[T] {
@@ -292,7 +326,7 @@ class FunctionTemplate[T] private(
   override def toString : String = str
 
   override def instantiate(aVar: T, args: Seq[T]): (Seq[T], Map[T, Set[TemplateCallInfo[T]]], Map[(T, App[T]), Set[TemplateAppInfo[T]]]) = {
-    if (!isRealFunDef) lambdaManager.registerFree(tfd.params.map(_.getType) zip args)
+    if (!isRealFunDef) manager.registerFree(tfd.params.map(_.getType) zip args)
     super.instantiate(aVar, args)
   }
 }
@@ -343,7 +377,7 @@ object LambdaTemplate {
   def apply[T](
     ids: (Identifier, T),
     encoder: TemplateEncoder[T],
-    lambdaManager: LambdaManager[T],
+    manager: QuantificationManager[T],
     pathVar: (Identifier, T),
     arguments: Seq[(Identifier, T)],
     condVars: Map[Identifier, T],
@@ -357,7 +391,7 @@ object LambdaTemplate {
 
     val id = ids._2
     val tpe = ids._1.getType.asInstanceOf[FunctionType]
-    val (clauses, blockers, applications, templateString) =
+    val (clauses, blockers, applications, matchers, templateString) =
       Template.encode(encoder, pathVar, arguments, condVars, exprVars, guardedExprs, lambdas,
         substMap = baseSubstMap + ids, optApp = Some(id -> tpe))
 
@@ -370,7 +404,7 @@ object LambdaTemplate {
     new LambdaTemplate[T](
       ids._1,
       encoder,
-      lambdaManager,
+      manager,
       pathVar._2,
       arguments.map(_._2),
       condVars,
@@ -378,6 +412,7 @@ object LambdaTemplate {
       clauses,
       blockers,
       applications,
+      matchers,
       lambdas,
       keyDeps,
       key,
@@ -389,7 +424,7 @@ object LambdaTemplate {
 class LambdaTemplate[T] private (
   val id: Identifier,
   val encoder: TemplateEncoder[T],
-  val lambdaManager: LambdaManager[T],
+  val manager: QuantificationManager[T],
   val start: T,
   val args: Seq[T],
   val condVars: Map[Identifier, T],
@@ -397,6 +432,7 @@ class LambdaTemplate[T] private (
   val clauses: Seq[T],
   val blockers: Map[T, Set[TemplateCallInfo[T]]],
   val applications: Map[T, Set[App[T]]],
+  val matchers: Map[T, Set[Matcher[T]]],
   val lambdas: Map[T, LambdaTemplate[T]],
   private[templates] val dependencies: Map[Identifier, T],
   private[templates] val structuralKey: Lambda,
@@ -422,6 +458,11 @@ class LambdaTemplate[T] private (
       bp -> fas.map(fa => fa.copy(caller = substituter(fa.caller), args = fa.args.map(substituter)))
     }
 
+    val newMatchers = matchers.map { case (b, ms) =>
+      val bp = if (b == start) newStart else b
+      bp -> ms.map(m => m.copy(caller = substituter(m.caller), args = m.args.map(substituter)))
+    }
+
     val newLambdas = lambdas.map { case (idT, template) => idT -> template.substitute(substMap) }
 
     val newDependencies = dependencies.map(p => p._1 -> substituter(p._2))
@@ -429,7 +470,7 @@ class LambdaTemplate[T] private (
     new LambdaTemplate[T](
       id,
       encoder,
-      lambdaManager,
+      manager,
       newStart,
       args,
       condVars,
@@ -437,6 +478,7 @@ class LambdaTemplate[T] private (
       newClauses,
       newBlockers,
       newApplications,
+      newMatchers,
       newLambdas,
       newDependencies,
       structuralKey,
diff --git a/src/main/scala/leon/solvers/templates/UnrollingBank.scala b/src/main/scala/leon/solvers/templates/UnrollingBank.scala
index abd50bb39..c1912d9d5 100644
--- a/src/main/scala/leon/solvers/templates/UnrollingBank.scala
+++ b/src/main/scala/leon/solvers/templates/UnrollingBank.scala
@@ -91,7 +91,9 @@ class UnrollingBank[T <% Printable](ctx: LeonContext, templateGenerator: Templat
 
   def canUnroll = callInfo.nonEmpty || appInfo.nonEmpty
 
-  def currentBlockers = callInfo.map(_._2._3).toSeq ++ appInfo.map(_._2._4).toSeq ++ manager.blockers
+  def currentBlockers = callInfo.map(_._2._3).toSeq ++ appInfo.map(_._2._4).toSeq
+
+  def quantificationAssumptions = manager.blockers
 
   def getBlockersToUnlock: Seq[T] = {
     if (callInfo.isEmpty && appInfo.isEmpty) {
diff --git a/src/main/scala/leon/solvers/z3/FairZ3Solver.scala b/src/main/scala/leon/solvers/z3/FairZ3Solver.scala
index f858ef12d..b8eeeafab 100644
--- a/src/main/scala/leon/solvers/z3/FairZ3Solver.scala
+++ b/src/main/scala/leon/solvers/z3/FairZ3Solver.scala
@@ -268,7 +268,7 @@ class FairZ3Solver(val context: LeonContext, val program: Program)
 
       val timer = context.timers.solvers.z3.check.start()
       solver.push() // FIXME: remove when z3 bug is fixed
-      val res = solver.checkAssumptions((assumptionsAsZ3 ++ unrollingBank.currentBlockers) :_*)
+      val res = solver.checkAssumptions((assumptionsAsZ3 ++ unrollingBank.currentBlockers ++ unrollingBank.quantificationAssumptions) :_*)
       solver.pop()  // FIXME: remove when z3 bug is fixed
       timer.stop()
 
@@ -359,7 +359,7 @@ class FairZ3Solver(val context: LeonContext, val program: Program)
 
             val timer = context.timers.solvers.z3.check.start()
             solver.push() // FIXME: remove when z3 bug is fixed
-            val res2 = solver.checkAssumptions(assumptionsAsZ3 : _*)
+            val res2 = solver.checkAssumptions((assumptionsAsZ3 ++ unrollingBank.quantificationAssumptions) : _*)
             solver.pop()  // FIXME: remove when z3 bug is fixed
             timer.stop()
 
-- 
GitLab