From b63c0f5bec8ef4560dc88014f13629934306a8fc Mon Sep 17 00:00:00 2001 From: patrick-schultz Date: Mon, 22 Apr 2024 12:55:16 -0400 Subject: [PATCH] push through declarative bindings --- .../main/scala/is/hail/expr/ir/BaseIR.scala | 6 +- .../main/scala/is/hail/expr/ir/Binds.scala | 137 ++++------------ .../is/hail/expr/ir/ComputeUsesAndDefs.scala | 6 +- hail/src/main/scala/is/hail/expr/ir/Env.scala | 146 +++++++++--------- .../scala/is/hail/expr/ir/ForwardLets.scala | 2 +- .../scala/is/hail/expr/ir/FreeVariables.scala | 9 +- .../is/hail/expr/ir/NormalizeNames.scala | 2 +- .../main/scala/is/hail/expr/ir/Subst.scala | 4 +- .../scala/is/hail/expr/ir/agg/Extract.scala | 3 +- 9 files changed, 118 insertions(+), 197 deletions(-) diff --git a/hail/src/main/scala/is/hail/expr/ir/BaseIR.scala b/hail/src/main/scala/is/hail/expr/ir/BaseIR.scala index 607336e2e8c..33af99b9d3a 100644 --- a/hail/src/main/scala/is/hail/expr/ir/BaseIR.scala +++ b/hail/src/main/scala/is/hail/expr/ir/BaseIR.scala @@ -64,7 +64,7 @@ abstract class BaseIR { def forEachChildWithEnv[E <: GenericBindingEnv[E, Type]](env: E)(f: (BaseIR, E) => Unit): Unit = childrenSeq.view.zipWithIndex.foreach { case (child, i) => - val childEnv = Bindings.get(this, i, env) + val childEnv = env.extend(Bindings.get(this, i)) f(child, childEnv) } @@ -73,7 +73,7 @@ abstract class BaseIR { val newChildren = childrenSeq.toArray var res = this for (i <- newChildren.indices) { - val childEnv = Bindings.get(res, i, env) + val childEnv = env.extend(Bindings.get(res, i)) val child = newChildren(i) val newChild = f(child, childEnv) if (!(newChild eq child)) { @@ -90,7 +90,7 @@ abstract class BaseIR { f: (BaseIR, Int, E) => StackFrame[Unit] ): StackFrame[Unit] = childrenSeq.view.zipWithIndex.foreachRecur { case (child, i) => - val childEnv = Bindings.get(this, i, env) + val childEnv = env.extend(Bindings.get(this, i)) f(child, i, childEnv) } } diff --git a/hail/src/main/scala/is/hail/expr/ir/Binds.scala b/hail/src/main/scala/is/hail/expr/ir/Binds.scala index fd7615f1f53..cd00b5631ee 100644 --- a/hail/src/main/scala/is/hail/expr/ir/Binds.scala +++ b/hail/src/main/scala/is/hail/expr/ir/Binds.scala @@ -7,126 +7,47 @@ import is.hail.utils.FastSeq import scala.collection.mutable -object SegregatedBindingEnv { - def apply[A, B](env: BindingEnv[A]): SegregatedBindingEnv[A, B] = - SegregatedBindingEnv(env, env.dropBindings) +object Binds { + def apply(x: IR, v: String, i: Int): Boolean = + Bindings.get(x, i).eval.exists(_._1 == v) } -case class SegregatedBindingEnv[A, B]( - childEnvWithoutBindings: BindingEnv[A], - newBindings: BindingEnv[B], -) extends GenericBindingEnv[SegregatedBindingEnv[A, B], B] { - def newBlock( - eval: Seq[(String, B)] = Seq.empty, - agg: AggEnv[B] = AggEnv.NoOp, - scan: AggEnv[B] = AggEnv.NoOp, - relational: Seq[(String, B)] = Seq.empty, - dropEval: Boolean = false, - ): SegregatedBindingEnv[A, B] = - SegregatedBindingEnv( - childEnvWithoutBindings.newBlock(agg = agg.empty, scan = scan.empty, dropEval = dropEval), - newBindings.newBlock(eval, agg, scan, relational, dropEval), - ) - - def unified(implicit ev: BindingEnv[B] =:= BindingEnv[A]): BindingEnv[A] = - childEnvWithoutBindings.merge(newBindings) - - def mapNewBindings[C](f: (String, B) => C): SegregatedBindingEnv[A, C] = SegregatedBindingEnv( - childEnvWithoutBindings, - newBindings.mapValuesWithKey(f), - ) - - override def promoteAgg: SegregatedBindingEnv[A, B] = SegregatedBindingEnv( - childEnvWithoutBindings.promoteAgg, - newBindings.promoteAgg, - ) - - override def promoteScan: SegregatedBindingEnv[A, B] = SegregatedBindingEnv( - childEnvWithoutBindings.promoteScan, - newBindings.promoteScan, - ) - - override def bindEval(bindings: (String, B)*): SegregatedBindingEnv[A, B] = - copy(newBindings = newBindings.bindEval(bindings: _*)) - - override def noEval: SegregatedBindingEnv[A, B] = SegregatedBindingEnv( - childEnvWithoutBindings.copy(eval = Env.empty), - newBindings.copy(eval = Env.empty), - ) - - override def bindAgg(bindings: (String, B)*): SegregatedBindingEnv[A, B] = - copy(newBindings = newBindings.bindAgg(bindings: _*)) - - override def bindScan(bindings: (String, B)*): SegregatedBindingEnv[A, B] = - copy(newBindings = newBindings.bindScan(bindings: _*)) - - override def createAgg: SegregatedBindingEnv[A, B] = SegregatedBindingEnv( - childEnvWithoutBindings.createAgg, - newBindings.createAgg, - ) - - override def createScan: SegregatedBindingEnv[A, B] = SegregatedBindingEnv( - childEnvWithoutBindings.createScan, - newBindings.createScan, - ) - - override def noAgg: SegregatedBindingEnv[A, B] = SegregatedBindingEnv( - childEnvWithoutBindings.noAgg, - newBindings.noAgg, - ) - - override def noScan: SegregatedBindingEnv[A, B] = SegregatedBindingEnv( - childEnvWithoutBindings.noScan, - newBindings.noScan, +final case class Bindings[+T]( + eval: IndexedSeq[(String, T)] = FastSeq.empty, + agg: AggEnv[T] = AggEnv.NoOp, + scan: AggEnv[T] = AggEnv.NoOp, + relational: IndexedSeq[(String, T)] = FastSeq.empty, + dropEval: Boolean = false, +) { + def map[U](f: (String, T) => U): Bindings[U] = Bindings( + eval.map { case (n, v) => n -> f(n, v) }, + agg.map(f), + scan.map(f), + relational.map { case (n, v) => n -> f(n, v) }, + dropEval, ) - override def onlyRelational(keepAggCapabilities: Boolean = false): SegregatedBindingEnv[A, B] = - SegregatedBindingEnv( - childEnvWithoutBindings.onlyRelational(keepAggCapabilities), - newBindings.onlyRelational(keepAggCapabilities), - ) - - override def bindRelational(bindings: (String, B)*): SegregatedBindingEnv[A, B] = - copy(newBindings = newBindings.bindRelational(bindings: _*)) -} + def allEmpty: Boolean = + eval.isEmpty && agg.isEmpty && scan.isEmpty && relational.isEmpty -object Binds { - def apply(x: IR, v: String, i: Int): Boolean = - Bindings.get(x, i, BindingEnv.empty[Type].createAgg.createScan).eval.contains(v) + def dropBindings[U]: Bindings[U] = + Bindings(FastSeq.empty, agg.empty, scan.empty, FastSeq.empty, dropEval) } -final case class Bindings( - eval: IndexedSeq[(String, Type)] = FastSeq.empty, - agg: AggEnv[Type] = AggEnv.NoOp, - scan: AggEnv[Type] = AggEnv.NoOp, - relational: IndexedSeq[(String, Type)] = FastSeq.empty, - dropEval: Boolean = false, -) - object Bindings { - val empty: Bindings = Bindings(FastSeq.empty, AggEnv.NoOp, AggEnv.NoOp, FastSeq.empty, false) + val empty: Bindings[Nothing] = + Bindings(FastSeq.empty, AggEnv.NoOp, AggEnv.NoOp, FastSeq.empty, false) /** Returns the environment of the `i`th child of `ir` given the environment of the parent node * `ir`. */ - def get[E <: GenericBindingEnv[E, Type]](ir: BaseIR, i: Int, baseEnv: E): E = { - val bindings = ir match { + def get(ir: BaseIR, i: Int): Bindings[Type] = + ir match { case ir: MatrixIR => childEnvMatrix(ir, i) case ir: TableIR => childEnvTable(ir, i) case ir: BlockMatrixIR => childEnvBlockMatrix(ir, i) case ir: IR => childEnvValue(ir, i) } - baseEnv.extend(bindings) - } - - /** Like [[Bindings.get]], but keeps separate any new bindings introduced by `ir`. Always - * satisfies the identity - * {{{ - * Bindings.segregated(ir, i, baseEnv).unified == Bindings(ir, i, baseEnv) - * }}} - */ - def segregated[A](ir: BaseIR, i: Int, baseEnv: BindingEnv[A]): SegregatedBindingEnv[A, Type] = - get(ir, i, SegregatedBindingEnv(baseEnv)) // Create a `Bindings` which cannot see anything bound in the enclosing context. private def inFreshScope( @@ -134,7 +55,7 @@ object Bindings { agg: Option[IndexedSeq[(String, Type)]] = None, scan: Option[IndexedSeq[(String, Type)]] = None, relational: IndexedSeq[(String, Type)] = FastSeq.empty, - ): Bindings = Bindings( + ): Bindings[Type] = Bindings( eval, agg.map(AggEnv.Create(_)).getOrElse(AggEnv.Drop), scan.map(AggEnv.Create(_)).getOrElse(AggEnv.Drop), @@ -142,7 +63,7 @@ object Bindings { dropEval = true, ) - private def childEnvMatrix(ir: MatrixIR, i: Int): Bindings = { + private def childEnvMatrix(ir: MatrixIR, i: Int): Bindings[Type] = { ir match { case MatrixMapRows(child, _) if i == 1 => Bindings.inFreshScope( @@ -197,7 +118,7 @@ object Bindings { } } - private def childEnvTable(ir: TableIR, i: Int): Bindings = { + private def childEnvTable(ir: TableIR, i: Int): Bindings[Type] = { ir match { case TableFilter(child, _) if i == 1 => Bindings.inFreshScope(child.typ.rowBindings) @@ -239,7 +160,7 @@ object Bindings { } } - private def childEnvBlockMatrix(ir: BlockMatrixIR, i: Int): Bindings = { + private def childEnvBlockMatrix(ir: BlockMatrixIR, i: Int): Bindings[Type] = { ir match { case BlockMatrixMap(_, eltName, _, _) if i == 1 => Bindings.inFreshScope(FastSeq(eltName -> TFloat64)) @@ -252,7 +173,7 @@ object Bindings { } } - private def childEnvValue(ir: IR, i: Int): Bindings = + private def childEnvValue(ir: IR, i: Int): Bindings[Type] = ir match { case Block(bindings, _) => val eval = mutable.ArrayBuilder.make[(String, Type)] diff --git a/hail/src/main/scala/is/hail/expr/ir/ComputeUsesAndDefs.scala b/hail/src/main/scala/is/hail/expr/ir/ComputeUsesAndDefs.scala index 5ee4ed4d146..3980af5a5ad 100644 --- a/hail/src/main/scala/is/hail/expr/ir/ComputeUsesAndDefs.scala +++ b/hail/src/main/scala/is/hail/expr/ir/ComputeUsesAndDefs.scala @@ -42,10 +42,10 @@ object ComputeUsesAndDefs { ir.children .zipWithIndex .foreach { case (child, i) => - val bindings = Bindings.segregated(ir, i, env).mapNewBindings((_, _) => ir) - if (!bindings.newBindings.allEmpty && !uses.contains(ir)) + val newBindings = Bindings.get(ir, i).map((_, _) => ir) + if (!newBindings.allEmpty && !uses.contains(ir)) uses.bind(ir, mutable.Set.empty[RefEquality[BaseRef]]) - compute(child, bindings.unified) + compute(child, env.extend(newBindings)) } } diff --git a/hail/src/main/scala/is/hail/expr/ir/Env.scala b/hail/src/main/scala/is/hail/expr/ir/Env.scala index 6ffb70182e0..58e911ab517 100644 --- a/hail/src/main/scala/is/hail/expr/ir/Env.scala +++ b/hail/src/main/scala/is/hail/expr/ir/Env.scala @@ -1,7 +1,5 @@ package is.hail.expr.ir -import is.hail.types.virtual.Type - object Env { type K = String @@ -20,6 +18,22 @@ sealed abstract class AggEnv[+A] { case AggEnv.Drop => AggEnv.Drop case AggEnv.Promote => AggEnv.Promote } + + def map[B](f: (String, A) => B): AggEnv[B] = this match { + case AggEnv.Create(bindings) => AggEnv.Create(bindings.map { case (n, v) => n -> f(n, v) }) + case AggEnv.Bind(bindings) => AggEnv.Bind(bindings.map { case (n, v) => n -> f(n, v) }) + case AggEnv.NoOp => AggEnv.NoOp + case AggEnv.Drop => AggEnv.Drop + case AggEnv.Promote => AggEnv.Promote + } + + def isEmpty: Boolean = getBindings.forall(_.isEmpty) + + def getBindings: Option[Seq[(String, A)]] = this match { + case AggEnv.Create(bindings) => Some(bindings) + case AggEnv.Bind(bindings) => Some(bindings) + case _ => None + } } object AggEnv { @@ -33,24 +47,8 @@ object AggEnv { if (bindings.nonEmpty) Bind(bindings) else NoOp } -object GenericBindingEnv { - implicit class GenericBindingEnvType[Self](private val env: GenericBindingEnv[Self, Type]) - extends AnyVal { - def extend(bindings: Bindings): Self = { - val Bindings(eval, agg, scan, relational, dropEval) = bindings - env.newBlock(eval, agg, scan, relational, dropEval) - } - } -} - trait GenericBindingEnv[Self, V] { - def newBlock( - eval: Seq[(String, V)] = Seq.empty, - agg: AggEnv[V] = AggEnv.NoOp, - scan: AggEnv[V] = AggEnv.NoOp, - relational: Seq[(String, V)] = Seq.empty, - dropEval: Boolean = false, - ): Self + def extend(bindings: Bindings[V]): Self def promoteAgg: Self @@ -96,13 +94,14 @@ case class BindingEnv[V]( scan: Option[Env[V]] = None, relational: Env[V] = Env.empty[V], ) extends GenericBindingEnv[BindingEnv[V], V] { - def newBlock( - eval: Seq[(String, V)] = Seq.empty, - agg: AggEnv[V] = AggEnv.NoOp, - scan: AggEnv[V] = AggEnv.NoOp, - relational: Seq[(String, V)] = Seq.empty, - dropEval: Boolean = false, - ): BindingEnv[V] = { + + private def modifyWithoutNewBindings[T](bindings: Bindings[T]): BindingEnv[V] = { + def error(): Unit = + throw new RuntimeException(s"found inconsistent agg or scan environments:" + + s"\n env: agg is ${if (this.agg.isDefined) "" else "not "}defined, " + + s"scan is ${if (this.scan.isDefined) "" else "not "}defined" + + s"\n bindings: agg = ${bindings.agg}, scan = ${bindings.scan}") + val Bindings(_, agg, scan, _, dropEval) = bindings var newEnv = this if (dropEval) newEnv = newEnv.noEval if (agg.isInstanceOf[AggEnv.Create[V]] || scan.isInstanceOf[AggEnv.Create[V]]) @@ -110,17 +109,39 @@ case class BindingEnv[V]( newEnv.copy(agg = newEnv.agg.map(_ => Env.empty), scan = newEnv.scan.map(_ => Env.empty)) agg match { case AggEnv.Drop => newEnv = newEnv.noAgg - case AggEnv.Promote => newEnv = newEnv.promoteAgg - case AggEnv.Create(bindings) => - newEnv = newEnv.copy(agg = Some(newEnv.eval.bindIterable(bindings))) - case AggEnv.Bind(bindings) => newEnv = newEnv.bindAgg(bindings: _*) + case AggEnv.Promote => + if (newEnv.agg.isEmpty) error() + newEnv = newEnv.promoteAgg + case AggEnv.Create(_) => + newEnv = newEnv.copy(agg = Some(newEnv.eval)) + case AggEnv.Bind(_) => + if (newEnv.agg.isEmpty) error() case _ => } scan match { case AggEnv.Drop => newEnv = newEnv.noScan - case AggEnv.Promote => newEnv = newEnv.promoteScan - case AggEnv.Create(bindings) => - newEnv = newEnv.copy(scan = Some(newEnv.eval.bindIterable(bindings))) + case AggEnv.Promote => + if (newEnv.scan.isEmpty) error() + newEnv = newEnv.promoteScan + case AggEnv.Create(_) => + newEnv = newEnv.copy(scan = Some(newEnv.eval)) + case AggEnv.Bind(_) => + if (newEnv.scan.isEmpty) error() + case _ => + } + newEnv + } + + def extend(bindings: Bindings[V]): BindingEnv[V] = { + val Bindings(eval, agg, scan, relational, _) = bindings + var newEnv = modifyWithoutNewBindings(bindings) + agg match { + case AggEnv.Create(bindings) => newEnv = newEnv.bindAgg(bindings: _*) + case AggEnv.Bind(bindings) => newEnv = newEnv.bindAgg(bindings: _*) + case _ => + } + scan match { + case AggEnv.Create(bindings) => newEnv = newEnv.bindScan(bindings: _*) case AggEnv.Bind(bindings) => newEnv = newEnv.bindScan(bindings: _*) case _ => } @@ -129,6 +150,29 @@ case class BindingEnv[V]( newEnv } + def subtract[T](bindings: Bindings[T]): BindingEnv[V] = { + val Bindings(eval, agg, scan, relational, _) = bindings + var newEnv = modifyWithoutNewBindings(bindings) + agg match { + case AggEnv.Create(bindings) => + newEnv = newEnv.copy(agg = Some(newEnv.agg.get.delete(bindings.view.map(_._1)))) + case AggEnv.Bind(bindings) => + newEnv = newEnv.copy(agg = Some(newEnv.agg.get.delete(bindings.view.map(_._1)))) + case _ => + } + scan match { + case AggEnv.Create(bindings) => + newEnv = newEnv.copy(scan = Some(newEnv.scan.get.delete(bindings.view.map(_._1)))) + case AggEnv.Bind(bindings) => + newEnv = newEnv.copy(scan = Some(newEnv.scan.get.delete(bindings.view.map(_._1)))) + case _ => + } + if (eval.nonEmpty) newEnv = newEnv.copy(eval = newEnv.eval.delete(eval.view.map(_._1))) + if (relational.nonEmpty) + newEnv = newEnv.copy(relational = newEnv.relational.delete(relational.view.map(_._1))) + newEnv + } + def allEmpty: Boolean = eval.isEmpty && agg.forall(_.isEmpty) && scan.forall(_.isEmpty) && relational.isEmpty @@ -201,42 +245,6 @@ case class BindingEnv[V]( s"\n $k -> ${valuePrinter(v)}" }.mkString("")}""".stripMargin - def merge(newBindings: BindingEnv[V]): BindingEnv[V] = { - if (agg.isDefined != newBindings.agg.isDefined || scan.isDefined != newBindings.scan.isDefined) - throw new RuntimeException(s"found inconsistent agg or scan environments:" + - s"\n left: ${agg.isDefined}, ${scan.isDefined}" + - s"\n right: ${newBindings.agg.isDefined}, ${newBindings.scan.isDefined}") - if (allEmpty) - newBindings - else if (newBindings.allEmpty) - this - else { - copy( - eval = eval.bindIterable(newBindings.eval.m), - agg = agg.map(a => a.bindIterable(newBindings.agg.get.m)), - scan = scan.map(a => a.bindIterable(newBindings.scan.get.m)), - relational = relational.bindIterable(newBindings.relational.m), - ) - } - } - - def subtract(newBindings: BindingEnv[_]): BindingEnv[V] = { - if (agg.isDefined != newBindings.agg.isDefined || scan.isDefined != newBindings.scan.isDefined) - throw new RuntimeException(s"found inconsistent agg or scan environments:" + - s"\n left: ${agg.isDefined}, ${scan.isDefined}" + - s"\n right: ${newBindings.agg.isDefined}, ${newBindings.scan.isDefined}") - if (allEmpty || newBindings.allEmpty) - this - else { - copy( - eval = eval.delete(newBindings.eval.m.keys), - agg = agg.map(a => a.delete(newBindings.agg.get.m.keys)), - scan = scan.map(a => a.delete(newBindings.scan.get.m.keys)), - relational = relational.delete(newBindings.relational.m.keys), - ) - } - } - def mapValues[T](f: V => T): BindingEnv[T] = copy[T]( eval = eval.mapValues(f), diff --git a/hail/src/main/scala/is/hail/expr/ir/ForwardLets.scala b/hail/src/main/scala/is/hail/expr/ir/ForwardLets.scala index 58425ebcee3..3d9d056cec1 100644 --- a/hail/src/main/scala/is/hail/expr/ir/ForwardLets.scala +++ b/hail/src/main/scala/is/hail/expr/ir/ForwardLets.scala @@ -61,7 +61,7 @@ object ForwardLets { .getOrElse(x) case _ => ir.mapChildrenWithIndex((ir1, i) => - rewrite(ir1, Bindings.segregated(ir, i, env).childEnvWithoutBindings) + rewrite(ir1, env.extend(Bindings.get(ir, i).dropBindings)) ) } } diff --git a/hail/src/main/scala/is/hail/expr/ir/FreeVariables.scala b/hail/src/main/scala/is/hail/expr/ir/FreeVariables.scala index 6da03d3b1a6..8845920f1ee 100644 --- a/hail/src/main/scala/is/hail/expr/ir/FreeVariables.scala +++ b/hail/src/main/scala/is/hail/expr/ir/FreeVariables.scala @@ -26,13 +26,8 @@ case class FreeVariableBindingEnv( aggVars: Option[FreeVariableEnv], scanVars: Option[FreeVariableEnv], ) extends GenericBindingEnv[FreeVariableBindingEnv, Type] { - def newBlock( - eval: Seq[(String, Type)], - agg: AggEnv[Type], - scan: AggEnv[Type], - relational: Seq[(String, Type)], - dropEval: Boolean, - ): FreeVariableBindingEnv = { + def extend(bindings: Bindings[Type]): FreeVariableBindingEnv = { + val Bindings(eval, agg, scan, relational, dropEval) = bindings var newEnv = this if (dropEval) newEnv = newEnv.noEval agg match { diff --git a/hail/src/main/scala/is/hail/expr/ir/NormalizeNames.scala b/hail/src/main/scala/is/hail/expr/ir/NormalizeNames.scala index 6e5e780da4a..03da74781d2 100644 --- a/hail/src/main/scala/is/hail/expr/ir/NormalizeNames.scala +++ b/hail/src/main/scala/is/hail/expr/ir/NormalizeNames.scala @@ -288,7 +288,7 @@ class NormalizeNames(normFunction: Int => String, allowFreeVariables: Boolean = x.mapChildrenWithIndexStackSafe { (child, i) => normalizeBaseIR( child, - Bindings.segregated(x, i, env).mapNewBindings((name, _) => name).unified, + env.extend(Bindings.get(x, i).map((name, _) => name)), ) } } diff --git a/hail/src/main/scala/is/hail/expr/ir/Subst.scala b/hail/src/main/scala/is/hail/expr/ir/Subst.scala index 371064b0901..32f0fe8bc55 100644 --- a/hail/src/main/scala/is/hail/expr/ir/Subst.scala +++ b/hail/src/main/scala/is/hail/expr/ir/Subst.scala @@ -15,9 +15,7 @@ object Subst { env.eval.lookupOption(name).getOrElse(x) case _ => e.mapChildrenWithIndex { - case (child: IR, i) => - val bindings = Bindings.segregated(e, i, env) - subst(child, bindings.childEnvWithoutBindings.subtract(bindings.newBindings)) + case (child: IR, i) => subst(child, env.subtract(Bindings.get(e, i))) case (child, _) => child } } diff --git a/hail/src/main/scala/is/hail/expr/ir/agg/Extract.scala b/hail/src/main/scala/is/hail/expr/ir/agg/Extract.scala index 7b8c16e968a..5f7012da576 100644 --- a/hail/src/main/scala/is/hail/expr/ir/agg/Extract.scala +++ b/hail/src/main/scala/is/hail/expr/ir/agg/Extract.scala @@ -712,8 +712,7 @@ object Extract { x case x => x.mapChildrenWithIndex { case (child: IR, i) => - val newEnv = - Bindings.segregated(x, i, env).mapNewBindings((_, _) => RefEquality(x)).unified + val newEnv = env.extend(Bindings.get(x, i).map((_, _) => RefEquality(x))) this.extract(child, newEnv, bindingNodesReferenced, rewriteMap, ab, seqBuilder, memo, result, r, isScan)