Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Binding implementation to multiple interfaces #652

Merged
merged 9 commits into from
Nov 15, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 101 additions & 2 deletions distage/distage-core/src/test/scala/izumi/distage/dsl/DSLTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ package izumi.distage.dsl
import distage._
import izumi.distage.fixtures.BasicCases._
import izumi.distage.fixtures.SetCases._
import izumi.distage.model.definition.Binding.SetElementBinding
import izumi.distage.injector.MkInjector
import izumi.distage.model.definition.Binding.{SetElementBinding, SingletonBinding}
import izumi.distage.model.definition.{BindingTag, Bindings, ImplDef, Module}
import izumi.fundamentals.platform.functional.Identity
import izumi.fundamentals.platform.language.SourceFilePosition
import org.scalatest.WordSpec

class DSLTest extends WordSpec {
class DSLTest extends WordSpec with MkInjector {

import TestTagOps._

Expand Down Expand Up @@ -320,6 +323,102 @@ class DSLTest extends WordSpec {
assert(definition1.bindings.map(_.tags.strings) == Set(Set("tag1"), Set("tag2")))
assert(definition2.bindings.map(_.tags.strings) == Set(Set("tag1", "tag2")))
}

"support binding to multiple interfaces" in {
import BasicCase6._

val implXYZ = new ImplXYZ

val definition = new ModuleDef {
bind[ImplXYZ]
.to[TraitX]
.to[TraitY]
.to[TraitZ]
}

assert(definition === Module.make(
Set(
Bindings.binding[ImplXYZ]
, Bindings.reference[TraitX, ImplXYZ]
, Bindings.reference[TraitY, ImplXYZ]
, Bindings.reference[TraitZ, ImplXYZ]
)
))

val definitionEffect = new ModuleDef {
bindEffect[Identity, ImplXYZ](implXYZ).to[TraitX].to[TraitY].to[TraitZ]
}

assert(definitionEffect === Module.make(
Set(
SingletonBinding(DIKey.get[ImplXYZ], ImplDef.EffectImpl(SafeType.get[ImplXYZ], SafeType.getK[Identity],
ImplDef.InstanceImpl(SafeType.get[ImplXYZ], implXYZ)), Set.empty, SourceFilePosition.unknown)
, Bindings.reference[TraitX, ImplXYZ]
, Bindings.reference[TraitY, ImplXYZ]
, Bindings.reference[TraitZ, ImplXYZ]
)
))

val definitionResource = new ModuleDef {
bindResource[DIResource.Simple[ImplXYZ], ImplXYZ].to[TraitX].to[TraitY].to[TraitZ]
}

assert(definitionResource === Module.make(
Set(
SingletonBinding(DIKey.get[ImplXYZ], ImplDef.ResourceImpl(SafeType.get[ImplXYZ], SafeType.getK[Identity],
ImplDef.TypeImpl(SafeType.get[DIResource.Simple[ImplXYZ]])), Set.empty, SourceFilePosition.unknown)
, Bindings.reference[TraitX, ImplXYZ]
, Bindings.reference[TraitY, ImplXYZ]
, Bindings.reference[TraitZ, ImplXYZ]
)
))

}

"support bindings to multiple interfaces (injector test)" in {
import BasicCase6._

val definition = PlannerInput.noGc(new ModuleDef {
bind[ImplXYZ].named("my-impl")
.to[TraitX]
.to[TraitY]("Y")
})

val defWithoutSugar = PlannerInput.noGc(new ModuleDef {
make[ImplXYZ].named("my-impl")
make[TraitX].using[ImplXYZ]("my-impl")
make[TraitY].named("Y").using[ImplXYZ]("my-impl")
})

val defWithTags = PlannerInput.noGc(new ModuleDef {
bind[ImplXYZ].named("my-impl").tagged("tag1")
.to[TraitX]
.to[TraitY]
})

val defWithTagsWithoutSugar = PlannerInput.noGc(new ModuleDef {
make[ImplXYZ].named("my-impl").tagged("tag1")
make[TraitX].tagged("tag1").using[ImplXYZ]("my-impl")
make[TraitY].tagged("tag1").using[ImplXYZ]("my-impl")
})

val injector = mkInjector()
val plan1 = injector.plan(definition)
val plan2 = injector.plan(defWithoutSugar)
assert(plan1.definition == plan2.definition)

val plan3 = injector.plan(defWithTags)
val plan4 = injector.plan(defWithTagsWithoutSugar)
assert(plan3.definition == plan4.definition)

val context = injector.produceUnsafe(plan1)
val xInstance = context.get[TraitX]
val yInstance = context.get[TraitY]("Y")
val implInstance = context.get[ImplXYZ]("my-impl")

assert(xInstance eq implInstance)
assert(yInstance eq implInstance)
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -127,4 +127,25 @@ Forest fire, climbin' higher, real life, it can wait""")
class TestImpl1(val justASet: Set[TestDependency])
}

object BasicCase6 {
trait TraitX {
def x(): String
}

trait TraitY {
def y(): String
}

trait TraitZ {
def z(): String
}

class ImplXYZ extends TraitX with TraitY with TraitZ {
override def x(): String = "X"
override def y(): String = "Y"
override def z(): String = "Z"
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ object Bindings {
def instance[T: Tag](instance: T)(implicit pos: CodePositionMaterializer): SingletonBinding[DIKey.TypeKey] =
SingletonBinding(DIKey.get[T], ImplDef.InstanceImpl(SafeType.get[T], instance), Set.empty, pos.get.position)

def reference[T: Tag, I <: T: Tag](implicit pos: CodePositionMaterializer): SingletonBinding[DIKey.TypeKey] =
SingletonBinding(DIKey.get[T], ImplDef.ReferenceImpl(SafeType.get[I], DIKey.get[I], weak = false), Set.empty, pos.get.position)

def provider[T: Tag](function: ProviderMagnet[T])(implicit pos: CodePositionMaterializer): SingletonBinding[DIKey.TypeKey] =
SingletonBinding(DIKey.get[T], ImplDef.ProviderImpl(function.get.ret, function.get), Set.empty, pos.get.position)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import izumi.distage.AbstractLocator
import izumi.distage.model.definition.Binding.{EmptySetBinding, SetElementBinding, SingletonBinding}
import izumi.distage.model.definition.ImplDef.InstanceImpl
import izumi.distage.model.definition.dsl.AbstractBindingDefDSL
import izumi.distage.model.definition.dsl.AbstractBindingDefDSL.MultipleInstruction.ImplWithReference
import izumi.distage.model.definition.dsl.AbstractBindingDefDSL.SetInstruction.SetIdAll
import izumi.distage.model.definition.dsl.AbstractBindingDefDSL.SingletonInstruction.{SetId, SetImpl}
import izumi.distage.model.definition.dsl.AbstractBindingDefDSL._
Expand All @@ -24,13 +25,16 @@ import scala.collection.mutable
// TODO: shameless copypaste of [[ModuleDef]] for now; but we ARE able to unify all of LocatorDef, ModuleDef, TypeLevelDSL and [[Bindings]] DSLs into one!
trait LocatorDef
extends AbstractLocator
with AbstractBindingDefDSL[LocatorDef.BindDSL, LocatorDef.SetDSL] {
with AbstractBindingDefDSL[LocatorDef.BindDSL, LocatorDef.MultipleDSL, LocatorDef.SetDSL] {

override protected[distage] def finalizers[F[_] : universe.RuntimeDIUniverse.TagK]: Seq[PlanInterpreter.Finalizer[F]] = Seq.empty

override private[definition] def _bindDSL[T: RuntimeDIUniverse.Tag](ref: SingletonRef): LocatorDef.BindDSL[T] =
new definition.LocatorDef.BindDSL[T](ref, ref.key)

override private[definition] def _multipleDSL[T: Tag](ref: MultipleRef): LocatorDef.MultipleDSL[T] =
new definition.LocatorDef.MultipleDSL[T](ref)

override private[definition] def _setDSL[T: RuntimeDIUniverse.Tag](ref: SetRef): LocatorDef.SetDSL[T] =
new definition.LocatorDef.SetDSL[T](ref)

Expand Down Expand Up @@ -137,6 +141,26 @@ object LocatorDef {
protected def bind(impl: ImplDef): AfterBind
}

final class MultipleDSL[T]
(
protected val mutableState: MultipleRef
) extends MultipleDSLMutBase[T] {

def to[I <: T : Tag]: MultipleDSL[T] = {
addOp(ImplWithReference(DIKey.get[I]))(new MultipleDSL[T](_))
}

}

sealed trait MultipleDSLMutBase[T] {
protected def mutableState: MultipleRef

protected def addOp[R](op: MultipleInstruction)(newState: MultipleRef => R): R = {
newState(mutableState.append(op))
}
}


trait SetDSLBase[T, AfterAdd] {
final def addValue[I <: T : Tag](instance: I)(implicit pos: CodePositionMaterializer): AfterAdd =
appendElement(ImplDef.InstanceImpl(SafeType.get[I], instance))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,29 @@
package izumi.distage.model.definition.dsl

import izumi.distage.model.definition.Binding.{EmptySetBinding, ImplBinding, SetElementBinding, SingletonBinding}
import izumi.distage.model.definition.DIResource.{DIResourceBase, ResourceTag}
import izumi.distage.model.definition.dsl.AbstractBindingDefDSL.MultipleInstruction.ImplWithReference
import izumi.distage.model.definition.dsl.AbstractBindingDefDSL.SetElementInstruction.ElementAddTags
import izumi.distage.model.definition.dsl.AbstractBindingDefDSL.SetInstruction.{AddTagsAll, SetIdAll}
import izumi.distage.model.definition.dsl.AbstractBindingDefDSL.SingletonInstruction._
import izumi.distage.model.definition.dsl.AbstractBindingDefDSL.{BindingRef, SetRef, SingletonInstruction, SingletonRef}
import izumi.distage.model.definition.dsl.AbstractBindingDefDSL._
import izumi.distage.model.definition.{Binding, BindingTag, Bindings, ImplDef}
import izumi.distage.model.reflection.universe.RuntimeDIUniverse.{DIKey, IdContract, Tag}
import izumi.distage.model.providers.ProviderMagnet
import izumi.distage.model.reflection.universe.RuntimeDIUniverse.{DIKey, IdContract, SafeType, Tag, TagK}
import izumi.fundamentals.platform.language.Quirks._
import izumi.fundamentals.platform.language.{CodePositionMaterializer, SourceFilePosition}

import scala.collection.mutable

trait AbstractBindingDefDSL[BindDSL[_], SetDSL[_]] {
trait AbstractBindingDefDSL[BindDSL[_], MultipleDSL[_], SetDSL[_]] {
private[this] final val mutableState: mutable.ArrayBuffer[BindingRef] = _initialState

private[definition] def _initialState: mutable.ArrayBuffer[BindingRef] = mutable.ArrayBuffer.empty

private[definition] def _bindDSL[T: Tag](ref: SingletonRef): BindDSL[T]

private[definition] def _multipleDSL[T: Tag](ref: MultipleRef): MultipleDSL[T]

private[definition] def _setDSL[T: Tag](ref: SetRef): SetDSL[T]

private[definition] def frozenState: collection.Seq[Binding] = {
Expand Down Expand Up @@ -91,6 +96,58 @@ trait AbstractBindingDefDSL[BindDSL[_], SetDSL[_]] {
_setDSL(setRef)
}

final protected def bind[T: Tag](implicit pos: CodePositionMaterializer): MultipleDSL[T] = {
bindImpl[T](ImplDef.TypeImpl(SafeType.get[T]))
}

final protected def bind[T: Tag](instance: => T)(implicit pos: CodePositionMaterializer): MultipleDSL[T] = {
bind(ProviderMagnet.lift(instance))
}

final protected def bindValue[T: Tag](instance: T)(implicit pos: CodePositionMaterializer): MultipleDSL[T] = {
bindImpl[T](ImplDef.InstanceImpl(SafeType.get[T], instance))
}

final protected def bind[T: Tag](function: ProviderMagnet[T])(implicit pos: CodePositionMaterializer): MultipleDSL[T] = {
bindImpl[T](ImplDef.ProviderImpl(SafeType.get[T], function.get))
}

final protected def bindEffect[F[_] : TagK, T: Tag, EFF <: F[T] : Tag](implicit pos: CodePositionMaterializer): MultipleDSL[T] = {
bindImpl[T](ImplDef.EffectImpl(SafeType.get[T], SafeType.getK[F], ImplDef.TypeImpl(SafeType.get[EFF])))
}

final protected def bindEffect[F[_] : TagK, T: Tag](instance: F[T])(implicit pos: CodePositionMaterializer): MultipleDSL[T] = {
bindImpl[T](ImplDef.EffectImpl(SafeType.get[T], SafeType.getK[F], ImplDef.InstanceImpl(SafeType.get[F[T]], instance)))
}

final protected def bindEffect[F[_] : TagK, T: Tag](function: ProviderMagnet[F[T]])(implicit pos: CodePositionMaterializer): MultipleDSL[T] = {
bindImpl[T](ImplDef.EffectImpl(SafeType.get[T], SafeType.getK[F], ImplDef.ProviderImpl(SafeType.get[F[T]], function.get)))
}

final protected def bindResource[R <: DIResourceBase[Any, T], T](implicit tag: ResourceTag[R] {type A = T}, pos: CodePositionMaterializer): MultipleDSL[T] = {
import tag._
bindImpl[T](ImplDef.ResourceImpl(SafeType.get[A], SafeType.getK[F], ImplDef.TypeImpl(SafeType.get[R])))
}

final protected def bindResource[R <: DIResourceBase[Any, T], T](instance: R with DIResourceBase[Any, T])(implicit tag: ResourceTag[R] {type A = T}, pos: CodePositionMaterializer): MultipleDSL[T] = {
import tag._
bindImpl[T](ImplDef.ResourceImpl(SafeType.get[A], SafeType.getK[F], ImplDef.InstanceImpl(SafeType.get[R], instance)))
}

final protected def bindResource[R <: DIResourceBase[Any, T], T](function: ProviderMagnet[R with DIResourceBase[Any, T]])(implicit tag: ResourceTag[R] {type A = T}, pos: CodePositionMaterializer): MultipleDSL[T] = {
import tag._
bindImpl[T](ImplDef.ResourceImpl(SafeType.get[A], SafeType.getK[F], ImplDef.ProviderImpl(SafeType.get[R], function.get)))
}

final protected def bindResource[R0, R <: DIResourceBase[Any, T], T]
(function: ProviderMagnet[R0])(implicit adapt: ProviderMagnet[R0] => ProviderMagnet[R with DIResourceBase[Any, T]], tag: ResourceTag[R] {type A = T}, pos: CodePositionMaterializer): MultipleDSL[T] = {
import tag._
bindImpl[T](ImplDef.ResourceImpl(SafeType.get[A], SafeType.getK[F], ImplDef.ProviderImpl(SafeType.get[R], adapt(function).get)))
}

// @deprecated("use .setOf", "14.03.2019")
// final protected def many[T: Tag](implicit pos: CodePositionMaterializer): SetDSL[T] = setOf[T]

/** Same as `make[T].from(implicitly[T])` **/
final protected def addImplicit[T: Tag](implicit instance: T, pos: CodePositionMaterializer): Unit = {
registered(new SingletonRef(Bindings.binding(instance))).discard()
Expand All @@ -101,6 +158,10 @@ trait AbstractBindingDefDSL[BindDSL[_], SetDSL[_]] {
registered(new SingletonRef(Bindings.binding(instance), mutable.Queue(SingletonInstruction.SetId(name)))).discard()
}

private[distage] def bindImpl[T: Tag](implDef: ImplDef)(implicit pos: CodePositionMaterializer): MultipleDSL[T] = {
val ref = registered(new MultipleRef(SingletonBinding(DIKey.get[T], implDef, Set.empty, pos.get.position), pos.get.position))
_multipleDSL[T](ref)
}
}

object AbstractBindingDefDSL {
Expand Down Expand Up @@ -132,6 +193,33 @@ object AbstractBindingDefDSL {
}
}

final class MultipleRef(initial: SingletonBinding[DIKey.TypeKey], pos: SourceFilePosition, ops: mutable.Queue[MultipleInstruction] = mutable.Queue.empty) extends BindingRef {
override def interpret: collection.Seq[ImplBinding] = {
val (init, tags, refs) = ops.foldLeft((initial: SingletonBinding[DIKey.BasicKey], Set.empty[BindingTag], Seq.empty[SingletonBinding[DIKey]])) {
case ((base, tagAcc, refs), instr) =>
instr match {
case s: MultipleInstruction.SetId[_] => (base.withTarget(DIKey.IdKey(base.key.tpe, s.id)(s.idContract)), tagAcc, refs)
case MultipleInstruction.AddTags(tags) => (base, tagAcc ++ tags, refs)
case ImplWithReference(key) =>
(base, tagAcc, SingletonBinding(key, ImplDef.ReferenceImpl(base.implementation.implType, base.key, weak = false), Set.empty, pos) +: refs)
}
}

init.addTags(tags) :: refs.reverse.map(_.addTags(tags)).toList
}

def key: DIKey.TypeKey = initial.key

def append(op: MultipleInstruction): MultipleRef = {
ops += op
this
}
}

private final class MultiSetHackId(private val long: Long) extends AnyVal {
override def toString: String = s"multi.${long.toString}"
}

final class SetRef
(
initial: EmptySetBinding[DIKey.TypeKey],
Expand Down Expand Up @@ -226,6 +314,19 @@ object AbstractBindingDefDSL {
final case class SetId[I](id: I)(implicit val idContract: IdContract[I]) extends SingletonInstruction

final case class SetIdFromImplName() extends SingletonInstruction

}

sealed trait MultipleInstruction

object MultipleInstruction {

final case class AddTags(tags: Set[BindingTag]) extends MultipleInstruction

final case class SetId[I](id: I)(implicit val idContract: IdContract[I]) extends MultipleInstruction

final case class ImplWithReference(key: DIKey) extends MultipleInstruction

}

sealed trait SetInstruction
Expand Down
Loading