Skip to content

Commit

Permalink
di (feature): Add scalafmt-friendly DI bind syntaxes (#3567)
Browse files Browse the repository at this point in the history
  • Loading branch information
xerial authored Jun 19, 2024
1 parent 81efadf commit 6284e97
Show file tree
Hide file tree
Showing 5 changed files with 187 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,86 @@ private[wvlet] object AirframeMacros {
h.registerTraitFactory(t)
}

def designBindInstanceImpl[A: c.WeakTypeTag](c: sm.Context)(obj: c.Tree): c.Tree = {
import c.universe.*
val t = implicitly[c.WeakTypeTag[A]].tpe
q"""${c.prefix}.bind[${t}].toInstance(${obj})"""
}

def designBindSingletonImpl[A: c.WeakTypeTag](c: sm.Context): c.Tree = {
import c.universe.*
val t = implicitly[c.WeakTypeTag[A]].tpe
q"""${c.prefix}.bind[${t}].toSingleton"""
}

def designBindImplImpl[A: c.WeakTypeTag, B: c.WeakTypeTag](c: sm.Context): c.Tree = {
import c.universe.*
val t = implicitly[c.WeakTypeTag[A]].tpe
q"""${c.prefix}.bind[${t}].to[${implicitly[c.WeakTypeTag[B]].tpe}]"""
}

def designBindProvider1Impl[D1: c.WeakTypeTag, A: c.WeakTypeTag](c: sm.Context)(f: c.Tree): c.Tree = {
import c.universe.*
val t = implicitly[c.WeakTypeTag[A]].tpe
val d1 = implicitly[c.WeakTypeTag[D1]].tpe
q"""${c.prefix}.bind[${t}].toProvider[${d1}](${f})"""
}

def designBindProvider2Impl[D1: c.WeakTypeTag, D2: c.WeakTypeTag, A: c.WeakTypeTag](
c: sm.Context
)(f: c.Tree): c.Tree = {
import c.universe.*
val t = implicitly[c.WeakTypeTag[A]].tpe
val d1 = implicitly[c.WeakTypeTag[D1]].tpe
val d2 = implicitly[c.WeakTypeTag[D2]].tpe
q"""${c.prefix}.bind[${t}].toProvider[${d1}, ${d2}](${f})"""
}

def designBindProvider3Impl[D1: c.WeakTypeTag, D2: c.WeakTypeTag, D3: c.WeakTypeTag, A: c.WeakTypeTag](
c: sm.Context
)(f: c.Tree): c.Tree = {
import c.universe.*
val t = implicitly[c.WeakTypeTag[A]].tpe
val d1 = implicitly[c.WeakTypeTag[D1]].tpe
val d2 = implicitly[c.WeakTypeTag[D2]].tpe
val d3 = implicitly[c.WeakTypeTag[D3]].tpe
q"""${c.prefix}.bind[${t}].toProvider[${d1}, ${d2}, ${d3}](${f})"""
}

def designBindProvider4Impl[
D1: c.WeakTypeTag,
D2: c.WeakTypeTag,
D3: c.WeakTypeTag,
D4: c.WeakTypeTag,
A: c.WeakTypeTag
](c: sm.Context)(f: c.Tree): c.Tree = {
import c.universe.*
val t = implicitly[c.WeakTypeTag[A]].tpe
val d1 = implicitly[c.WeakTypeTag[D1]].tpe
val d2 = implicitly[c.WeakTypeTag[D2]].tpe
val d3 = implicitly[c.WeakTypeTag[D3]].tpe
val d4 = implicitly[c.WeakTypeTag[D4]].tpe
q"""${c.prefix}.bind[${t}].toProvider[${d1}, ${d2}, ${d3}, ${d4}](${f})"""
}

def designBindProvider5Impl[
D1: c.WeakTypeTag,
D2: c.WeakTypeTag,
D3: c.WeakTypeTag,
D4: c.WeakTypeTag,
D5: c.WeakTypeTag,
A: c.WeakTypeTag
](c: sm.Context)(f: c.Tree): c.Tree = {
import c.universe.*
val t = implicitly[c.WeakTypeTag[A]].tpe
val d1 = implicitly[c.WeakTypeTag[D1]].tpe
val d2 = implicitly[c.WeakTypeTag[D2]].tpe
val d3 = implicitly[c.WeakTypeTag[D3]].tpe
val d4 = implicitly[c.WeakTypeTag[D4]].tpe
val d5 = implicitly[c.WeakTypeTag[D5]].tpe
q"""${c.prefix}.bind[${t}].toProvider[${d1}, ${d2}, ${d3}, ${d4}, ${d5}](${f})"""
}

def designBindImpl[A: c.WeakTypeTag](c: sm.Context): c.Tree = {
import c.universe.*
val t = implicitly[c.WeakTypeTag[A]].tpe
Expand Down
12 changes: 12 additions & 0 deletions airframe-di/src/main/scala-2/wvlet/airframe/DesignImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,18 @@ private[airframe] trait DesignImpl extends LogSupport {
def bind[A]: Binder[A] = macro AirframeMacros.designBindImpl[A]
def remove[A]: Design = macro AirframeMacros.designRemoveImpl[A]

def bindInstance[A](obj: A): Design = macro AirframeMacros.designBindInstanceImpl[A]
def bindSingleton[A]: Design = macro AirframeMacros.designBindSingletonImpl[A]
def bindImpl[A, B <: A]: Design = macro AirframeMacros.designBindImplImpl[A, B]
def bindProvider[D1, A](f: D1 => A): Design = macro AirframeMacros.designBindProvider1Impl[D1, A]
def bindProvider[D1, D2, A](f: (D1, D2) => A): Design = macro AirframeMacros.designBindProvider2Impl[D1, D2, A]
def bindProvider[D1, D2, D3, A](f: (D1, D2, D3) => A): Design =
macro AirframeMacros.designBindProvider3Impl[D1, D2, D3, A]
def bindProvider[D1, D2, D3, D4, A](f: (D1, D2, D3, D4) => A): Design =
macro AirframeMacros.designBindProvider4Impl[D1, D2, D3, D4, A]
def bindProvider[D1, D2, D3, D4, D5, A](f: (D1, D2, D3, D4, D5) => A): Design =
macro AirframeMacros.designBindProvider5Impl[D1, D2, D3, D4, D5, A]

/**
* A helper method of creating a new session and an instance of A. This method is useful when you only need to use A
* as an entry point of your program. After executing the body, the sesion will be closed.
Expand Down
17 changes: 17 additions & 0 deletions airframe-di/src/main/scala-3/wvlet/airframe/DesignImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,23 @@ private[airframe] trait DesignImpl extends LogSupport:
val target = Surface.of[A]
new Design(self.designOptions, self.binding.filterNot(_.from == target), self.hooks)

inline def bindInstance[A](obj: A): Design =
bind[A].toInstance(obj)
inline def bindSingleton[A]: Design =
bind[A].toSingleton
inline def bindImpl[A, B <: A]: Design =
bind[A].to[B]
inline def bindProvider[D1, A](f: D1 => A): Design =
bind[A].toProvider[D1](f)
inline def bindProvider[D1, D2, A](f: (D1, D2) => A): Design =
bind[A].toProvider[D1, D2](f)
inline def bindProvider[D1, D2, D3, A](f: (D1, D2, D3) => A): Design =
bind[A].toProvider[D1, D2, D3](f)
inline def bindProvider[D1, D2, D3, D4, A](f: (D1, D2, D3, D4) => A): Design =
bind[A].toProvider[D1, D2, D3, D4](f)
inline def bindProvider[D1, D2, D3, D4, D5, A](f: (D1, D2, D3, D4, D5) => A): Design =
bind[A].toProvider[D1, D2, D3, D4, D5](f)

/**
* A helper method of creating a new session and an instance of A. This method is useful when you only need to use A
* as an entry point of your program. After executing the body, the sesion will be closed.
Expand Down
54 changes: 54 additions & 0 deletions airframe-di/src/test/scala/wvlet/airframe/di/NewDISyntaxTest.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package wvlet.airframe.di

import wvlet.airframe.*
import wvlet.airspec.AirSpec

object NewDISyntaxTest extends AirSpec {
trait A
case class AImpl() extends A
case class B(s: String)
case class D1(n: String)
case class D3(d1: Int, d2: Long, d3: String)
case class D4(i: Int, l: Long, d: String, b: B)
case class D5(i: Int, l: Long, d: String, b: B, c: D3)

test("new bind syntax") {
val d = newDesign
.bindInstance[String]("hello")
.bindSingleton[B]
.bindImpl[A, AImpl]
.bindProvider[String, Int] { (s: String) => s.length }
.bindProvider { (a: A) => D1(a.toString) }
.bindProvider { (i: Int, s: String) => (i + s.length).toLong }
.bindProvider { (a: Int, b: Long, c: String) => D3(a, b, c) }
.bindProvider { (a: Int, b: Long, c: String, d: B) => D4(a, b, c, d) }
.bindProvider { (a: Int, b: Long, c: String, d: B, cc: D3) => D5(a, b, c, d, cc) }
.noLifeCycleLogging

d.withSession { session =>
session.build[String] shouldBe "hello"
session.build[B] shouldBe B("hello")
session.build[A] shouldBe AImpl()
session.build[Int] shouldBe 5
session.build[D1] shouldBe D1("AImpl()")

session.build[Long] shouldBe 10L
session.build[D3] shouldBe D3(5, 10, "hello")
session.build[D4] shouldBe D4(5, 10, "hello", B("hello"))
session.build[D5] shouldBe D5(5, 10, "hello", B("hello"), D3(5, 10, "hello"))
}
}
}
24 changes: 24 additions & 0 deletions docs/airframe-di.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,15 @@ val d = newDesign
.bind[Y].to[YImpl]
```

Alternatively, you can use new bind syntaxes introduced in Airframe 24.6.1:

```scala
val d = newDesign
.bindInstance[X](...)
.bindImpl[Y, YImpl]
```


### Basic Usage

First, create a class that has some parameters as dependencies. For example, the following code defines an App class having X, Y, and Z as its dependencies:
Expand Down Expand Up @@ -144,6 +153,21 @@ val design: Design =

If you define multiple bindings to the same type (e.g., P), the last binding will have the highest precedence.

Single version 24.6.1, Airframe DI supports the following short-hand binding syntaxes:

```scala
val design: Design =
newDesign
.bindSingleton[A] // Bind A to a singleton instance of A
.bindInstance[B](new B(1)) // Bind B to a concrete instance of B
.bindImpl[A, AImpl] // Bind A to AImpl
.bindProvider{ (d1:D1) => P(d1) } // Bind P using a provider function
.bindProvider{ (d1: D1, d2: D2) => P(d1, d2) } // Bind P using a provider function
...
.bindProvider{ (d1 D1, ..., d5: D5) => P(d1, ..., d5) } // Up to 5 arguments
```
This syntax works well with code formatter tools like [scalafmt](https://scalameta.org/scalafmt/).


### Design is Immutable

Expand Down

0 comments on commit 6284e97

Please sign in to comment.