├── .github └── workflows │ └── scala.yml ├── .gitignore ├── .scalafmt.conf ├── README.md ├── build.sbt ├── modules ├── core │ └── src │ │ ├── main │ │ └── scala │ │ │ └── make │ │ │ ├── Debug.scala │ │ │ ├── Dep.scala │ │ │ ├── Graph.scala │ │ │ ├── Make.scala │ │ │ ├── Tag.scala │ │ │ ├── annotated.scala │ │ │ ├── autoMake.scala │ │ │ ├── enableDebug.scala │ │ │ ├── internal │ │ │ ├── DebugStMacro.scala │ │ │ ├── DebugStateMacro.scala │ │ │ ├── DepMacro.scala │ │ │ ├── MacroState.scala │ │ │ ├── MakeAnnotationMacro.scala │ │ │ ├── MakeBasicOps.scala │ │ │ ├── MakeMacro.scala │ │ │ ├── MakeOps.scala │ │ │ ├── SourcePosMacro.scala │ │ │ ├── Tarjans.scala │ │ │ └── TpeTagMacro.scala │ │ │ └── syntax.scala │ │ └── test │ │ ├── java │ │ └── anno │ │ │ └── Sample.java │ │ └── scala │ │ └── make │ │ ├── MakeTest.scala │ │ ├── TagTest.scala │ │ └── itCompiles.scala └── example │ └── src │ └── main │ └── scala │ └── example │ ├── ExampleCatsEffect.scala │ ├── ExampleZio.scala │ └── FromReadme.scala └── project ├── Boilerplate.scala ├── build.properties └── plugins.sbt /.github/workflows/scala.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | 6 | name: Scala CI 7 | 8 | on: 9 | push: 10 | branches: [ "master" ] 11 | pull_request: 12 | branches: [ "master" ] 13 | 14 | permissions: 15 | contents: read 16 | 17 | jobs: 18 | build: 19 | 20 | runs-on: ubuntu-latest 21 | 22 | steps: 23 | - uses: actions/checkout@v4 24 | - name: Set up JDK 11 25 | uses: actions/setup-java@v4 26 | with: 27 | java-version: '11' 28 | distribution: 'temurin' 29 | cache: 'sbt' 30 | - name: Run tests 31 | run: sbt test 32 | # Optional: This step uploads information to the GitHub dependency graph and unblocking Dependabot alerts for the repository 33 | - name: Upload dependency graph 34 | uses: scalacenter/sbt-dependency-submission@ab086b50c947c9774b70f39fc7f6e20ca2706c91 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.log 3 | 4 | # sbt specific 5 | .cache 6 | .history 7 | .lib/ 8 | dist/* 9 | target/ 10 | lib_managed/ 11 | src_managed/ 12 | project/boot/ 13 | project/plugins/project/ 14 | **/.bloop 15 | 16 | # Scala-IDE specific 17 | .scala_dependencies 18 | .worksheet 19 | 20 | .idea 21 | 22 | # ENSIME specific 23 | .ensime_cache/ 24 | .ensime 25 | 26 | .metals/ 27 | metals.sbt 28 | metals/project/ 29 | 30 | .vscode/ 31 | 32 | local.* 33 | 34 | .DS_Store 35 | 36 | node_modules 37 | 38 | lib/core/metadata.js 39 | lib/core/MetadataBlog.js 40 | 41 | website/translated_docs 42 | website/build/ 43 | website/yarn.lock 44 | website/node_modules 45 | website/i18n/* 46 | !website/i18n/en.json 47 | 48 | project/metals.sbt 49 | -------------------------------------------------------------------------------- /.scalafmt.conf: -------------------------------------------------------------------------------- 1 | version = 2.7.3 2 | maxColumn = 100 3 | continuationIndent.defnSite = 2 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Make 2 | 3 | `Make` is an idiomatic version of the thing that is known as `dependency injection` but expressed in typeclass form 4 | that makes it usage close to usual Scala code and provides compile-time checks. 5 | 6 | ### Usage 7 | 8 | `build.sbt`: 9 | ```scala 10 | libraryDependencies += "io.github.dos65" %% "make" % "0.0.2" 11 | ``` 12 | 13 | Imports: 14 | ```scala 15 | import make._ 16 | import make.syntax._ 17 | ``` 18 | 19 | ### Defining instances 20 | 21 | Speaking roughly, `Make` might be treated as a typeclass with the following signature: 22 | ```scala 23 | trait Make[F[_], A] { 24 | def make: F[A] 25 | } 26 | ``` 27 | 28 | Where: 29 | - `F[_]` is an initialization effect 30 | - `A` a target type 31 | 32 | Working with it isn't different from how we usually work with typeclasses. 33 | 34 | Instance definition examples: 35 | ```scala 36 | // define instance 37 | case class Foo(a: Int) 38 | object Foo { 39 | // just a pure instance 40 | implicit val make: Make[IO, Foo] = Make.pure(Foo(42)) 41 | // or construct it in F[_] 42 | implicit val make: Make[IO, Foo] = Make.eff(IO(Foo(42))) 43 | } 44 | 45 | case class Bar(foo: Foo) 46 | object Bar { 47 | // define instance that is is build from dependency 48 | implicit def make(implicit dep: Make[IO, Foo]): Make[IO, Bar] = 49 | dep.map(foo => Bar(foo)) 50 | } 51 | 52 | case class Baz(foo: Foo, bar: Bar) 53 | object Baz { 54 | // use tuple for several depencies 55 | implicit def make(implicit dep: Make[IO, (Foo, Bar)]): Make[IO, Baz] = 56 | dep.mapN((foo, bar) => Baz(foo, bar)) 57 | } 58 | 59 | // or use @autoMake annotation to generate the code above 60 | @autoMake 61 | class AutoBaz(foo: Foo, bar: Bar) 62 | ``` 63 | 64 | Then summon instances that you need and use: 65 | ```scala 66 | // single Foo 67 | val fooMake: Make[IO, Foo] = Make.of[IO, Foo] 68 | // several instances 69 | val several: Make[IO, (Baz, AutoBaz)] = Make.of[IO, (Baz, AutoBaz)] 70 | 71 | // use 72 | import make.syntax._ 73 | 74 | val fooIO: IO[Foo] = fooMake.make 75 | ``` 76 | 77 | ### Debug 78 | 79 | In case if instance can't be infered it isn't clear what went wrong. 80 | To get a detailed information `debugOf`: 81 | ```scala 82 | val bazMake = Make.of[IO, Baz] 83 | // [error] FromReadme.scala:39:26: could not find implicit value for parameter m: make.Make[cats.effect.IO,example.FromReadme.Baz] 84 | // could not find implicit value for parameter m: make.Make[cats.effect.IO,example.FromReadme.Baz] 85 | // [error] val bazMake = Make.of[IO, Baz] 86 | // [error] ^ 87 | 88 | 89 | // detailed error with debug 90 | import make.enableDebug._ 91 | val bazMake = Make.debugOf[IO, Baz] 92 | // [error] FromReadme.scala:40:31: Make for example.FromReadme.Baz not found 93 | // [error] Make instance for example.FromReadme.Baz: 94 | // [error] Failed at example.FromReadme.Baz.make becase of: 95 | // [error] Make instance for (example.FromReadme.Foo, example.FromReadme.Bar): 96 | // [error] Failed at make.MakeTupleInstances.tuple2 becase of: 97 | // [error] Make instance for example.FromReadme.Foo: 98 | // [error] Make instance for example.FromReadme.Foo not found 99 | // [error] val bazMake = Make.debugOf[IO, Baz] 100 | // [error] ^ 101 | ``` 102 | 103 | ### Subtypes 104 | 105 | `A` type in `Make[F, A]` is invariant. 106 | So, to use interfaces in other instances you need to provide `ContraMake`: 107 | ```scala 108 | trait Foo 109 | 110 | @autoMake 111 | class FooImpl(smt: Smth) extends Foo 112 | 113 | implicit val fooFromFooImpl = ContraMake.widen[FooImpl, Foo] 114 | ``` 115 | 116 | ### Choose the F[_] 117 | 118 | It's up to you what `F[_]` to use. The only requirement on it is to have `cats.Monad` instance. 119 | `Resourse[F, ?]` should cover all needs. 120 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | import xerial.sbt.Sonatype._ 2 | 3 | lazy val commonSettings = Seq( 4 | scalaVersion := "2.12.12", 5 | organization := "io.github.dos65", 6 | version := "0.1.0-SNAPSHOT", 7 | crossScalaVersions := Seq("2.12.12", "2.13.3"), 8 | libraryDependencies ++= { 9 | if (is213(scalaVersion.value)) 10 | Seq.empty 11 | else 12 | Seq(compilerPlugin(("org.scalamacros" % "paradise" % "2.1.1").cross(CrossVersion.patch))) 13 | }, 14 | scalacOptions ++= { 15 | if(is213(scalaVersion.value)) 16 | Seq("-Ymacro-annotations") 17 | else 18 | Seq("-Ypartial-unification") 19 | }, 20 | libraryDependencies += "org.scalameta" %% "munit" % "0.4.3" % "test", 21 | testFrameworks += new TestFramework("munit.Framework"), 22 | ) 23 | 24 | lazy val publishSettings = Seq( 25 | publishMavenStyle := true, 26 | publishTo := sonatypePublishToBundle.value, 27 | sonatypeProfileName := "io.github.dos65", 28 | sonatypeProjectHosting := Some(GitHubHosting("dos65", "make", "qtankle@gmail.com")), 29 | licenses := Seq("Apache 2.0 License" -> url("http://www.apache.org/licenses/LICENSE-2.0")), 30 | sonatypeBundleDirectory := (ThisBuild / baseDirectory).value / "target" / "sonatype-staging" / s"${version.value}" 31 | ) 32 | 33 | lazy val make = project.in(file("modules/core")) 34 | .settings(commonSettings) 35 | .settings(publishSettings) 36 | .settings( 37 | name := "make", 38 | scalacOptions ++= Seq( 39 | "-language:experimental.macros", 40 | ), 41 | sourceGenerators in Compile += (sourceManaged in Compile).map(dir => Boilerplate.gen(dir)).taskValue, 42 | libraryDependencies ++= Seq( 43 | "org.scala-lang" % "scala-reflect" % scalaVersion.value, 44 | "org.scala-lang" % "scala-compiler" % scalaVersion.value, 45 | "org.typelevel" %% "cats-core" % "2.1.1", 46 | "com.chuusai" %% "shapeless" % "2.3.3" % "test", 47 | "org.typelevel" %% "cats-effect" % "2.1.3" % "test", 48 | ), 49 | ) 50 | 51 | lazy val example = project.in(file("modules/example")) 52 | .dependsOn(make) 53 | .settings(commonSettings) 54 | .settings( 55 | name := "make-example", 56 | libraryDependencies ++= Seq( 57 | "org.typelevel" %% "cats-effect" % "2.1.3", 58 | "dev.zio" %% "zio" % "1.0.1", 59 | "dev.zio" %% "zio-interop-cats" % "2.1.4.0", 60 | ), 61 | addCompilerPlugin("org.typelevel" %% "kind-projector" % "0.11.0" cross CrossVersion.full), 62 | skip in publish := true 63 | ) 64 | 65 | lazy val rootProject = project.in(file(".")) 66 | .settings(commonSettings) 67 | .settings(publishSettings) 68 | .settings( 69 | name := "make-root", 70 | skip in publish := true 71 | ) 72 | .aggregate(make) 73 | 74 | def is213(v: String): Boolean = { 75 | CrossVersion.partialVersion(v) match { 76 | case Some((2, 13)) => true 77 | case _ => false 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/make/Debug.scala: -------------------------------------------------------------------------------- 1 | package make 2 | 3 | case class Debug[+A](v: A) 4 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/make/Dep.scala: -------------------------------------------------------------------------------- 1 | package make 2 | 3 | import make.internal.DepMacro 4 | 5 | /** 6 | * This class is used to workaround wrong `divergency implicit error` on scala 2.12 7 | * Macros uses similar trick as `shapeless.Lazy` but do not allows recursive references 8 | */ 9 | case class Dep[F[_], A](value: Make[F, A]) 10 | 11 | object Dep { 12 | 13 | implicit def materialize[F[_], A]: Dep[F, A] = 14 | macro DepMacro.materialize[F, A] 15 | } -------------------------------------------------------------------------------- /modules/core/src/main/scala/make/Graph.scala: -------------------------------------------------------------------------------- 1 | package make 2 | 3 | import scala.reflect.runtime.universe.Type 4 | import make.Make.Value 5 | import make.Make.Bind 6 | import make.Make.Ap 7 | import scala.annotation.tailrec 8 | import make.Tag.SourcePos 9 | import make.internal.Tarjans 10 | import cats.Monad 11 | import cats.Applicative 12 | 13 | final class Graph[F[_], A]( 14 | entries: Map[Graph.Id, Graph.RawEntry[F]], 15 | targetId: Graph.Id 16 | )(implicit F: Monad[F]) { 17 | 18 | def initEff: F[A] = { 19 | val order = initOrder 20 | val init = F.pure(Map.empty[Graph.Id, Any]) 21 | 22 | val rs = order.foldLeft(init) { case (rs, id) => 23 | F.flatMap(rs)(depsMap => { 24 | 25 | val entry = entries(id) 26 | val input = entry.dependsOn.map(depsMap(_)) 27 | val rsc = entry.f(input.toList) 28 | F.map(rsc)(v => depsMap.updated(id, v)) 29 | }) 30 | } 31 | 32 | F.map(rs)(values => values(targetId).asInstanceOf[A]) 33 | } 34 | 35 | private def initOrder: List[Graph.Id] = { 36 | val indexedKeys = entries.keys.zipWithIndex.toMap 37 | val indexedMap = indexedKeys.map { case (tpe, _) => 38 | val entry = entries(tpe) 39 | entry.dependsOn.map(indexedKeys(_)).toList 40 | }.toList 41 | val sorted = Tarjans.apply(indexedMap) 42 | 43 | sorted.flatten 44 | .map(i => { 45 | val (tpe, idx) = indexedKeys.find(_._2 == i).get 46 | tpe 47 | }) 48 | .toList 49 | } 50 | 51 | } 52 | 53 | object Graph { 54 | 55 | case class Id(tpe: Tag.TpeTag.Type, pos: Tag.SourcePos) 56 | object Id { 57 | def fromTag[A](tag: Tag[A]): Id = 58 | Id(tag.typeTag.tpe, tag.sourcePos) 59 | } 60 | 61 | case class RawEntry[F[_]]( 62 | id: Id, 63 | dependsOn: List[Id], 64 | f: List[Any] => F[Any] 65 | ) 66 | 67 | def fromMake[F[_]: Monad, A](v: Make[F, A]): Graph[F, A] = { 68 | val allEntriesMap = makeToAllEntriesMap( 69 | Map.empty, 70 | List(v.asInstanceOf[Make[F, Any]]) 71 | ) 72 | new Graph(allEntriesMap, Id.fromTag(v.tag)) 73 | } 74 | 75 | @tailrec 76 | private def makeToAllEntriesMap[F[_]: Applicative]( 77 | acc: Map[Id, RawEntry[F]], 78 | stack: List[Make[F, Any]] 79 | ): Map[Id, RawEntry[F]] = { 80 | 81 | type HandleOut = (List[Any] => F[Any], List[Id], List[Make[F, Any]]) 82 | 83 | def handleNode(v: Make[F, Any]): HandleOut = { 84 | v match { 85 | case Make.Value(v, tag) => 86 | ((_: List[Any]) => v(), List.empty, List.empty) 87 | case x @ Make.Bind(prev, f, tag) => 88 | val func = (in: List[Any]) => f(in(0)) 89 | val deps = List(Id.fromTag(prev.tag)) 90 | val other = List(prev) 91 | (func, deps, other) 92 | case Make.Ap(prev, op, tag) => 93 | val func = 94 | (in: List[Any]) => { 95 | val a = in(0) 96 | val aToB = in(1).asInstanceOf[Any => Any] 97 | Applicative[F].pure[Any](aToB(a)) 98 | } 99 | val deps = List( 100 | Id.fromTag(prev.tag), 101 | Id.fromTag(op.tag) 102 | ) 103 | val other = List( 104 | prev, 105 | op.asInstanceOf[Make[F, Any]] 106 | ) 107 | (func, deps, other) 108 | } 109 | } 110 | 111 | def handleMake(make: Make[F, Any]): (RawEntry[F], List[Make[F, Any]]) = { 112 | val id = Id(make.tag.typeTag.tpe, make.tag.sourcePos) 113 | val (f, deps, toStack) = handleNode(make) 114 | val entry = RawEntry[F]( 115 | id, 116 | deps, 117 | f 118 | ) 119 | (entry, toStack) 120 | } 121 | 122 | stack match { 123 | case Nil => acc 124 | case mk :: tail => 125 | val tpe = mk.tag.typeTag.tpe 126 | val (entry, toStack) = handleMake(mk) 127 | 128 | val key = Id.fromTag(mk.tag) 129 | val nextAcc = acc.updated(key, entry) 130 | val nextStack = toStack ++ stack.tail 131 | makeToAllEntriesMap(nextAcc, nextStack) 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/make/Make.scala: -------------------------------------------------------------------------------- 1 | package make 2 | 3 | import make.internal.MakeMacro 4 | import make.internal.MakeOps 5 | import cats.Applicative 6 | import scala.reflect.runtime.universe.TypeTag 7 | import make.internal.MakeBasicOps 8 | import make.internal.DepMacro 9 | 10 | sealed abstract class Make[F[_], A] { 11 | def tag: Tag[A] 12 | } 13 | 14 | object Make extends ContraMakeInstances with MakeTupleInstances with LowPrioMake { 15 | 16 | def of[F[_], A](implicit m: Make[F, A]): Make[F, A] = m 17 | 18 | final private[make] case class Value[F[_], A]( 19 | v: () => F[A], 20 | tag: Tag[A] 21 | ) extends Make[F, A] 22 | 23 | final private[make] case class Bind[F[_], In, A]( 24 | prev: Make[F, In], 25 | f: In => F[A], 26 | tag: Tag[A] 27 | ) extends Make[F, A] 28 | 29 | final private[make] case class Ap[F[_], In, A]( 30 | prev: Make[F, In], 31 | f: Make[F, In => A], 32 | tag: Tag[A] 33 | ) extends Make[F, A] 34 | 35 | def pure[F[_]: Applicative, A: Tag](a: A): Make[F, A] = 36 | Value(() => Applicative[F].pure(a), Tag.of[A]) 37 | 38 | def eff[F[_], A: Tag](v: => F[A]): Make[F, A] = 39 | Value(() => v, Tag.of[A]) 40 | 41 | 42 | def widen[A, B <: A]: Contra[A, B] = new Contra[A, B](identity) 43 | def contramap[A, B](f: B => A): Contra[A, B] = new Contra[A, B](f) 44 | 45 | final class Contra[A, B](private[make] val f: B => A) 46 | 47 | } 48 | 49 | trait ContraMakeInstances { 50 | 51 | implicit def contraMakeInstance[F[_]: Applicative, A, B](implicit 52 | contra: Make.Contra[A, B], 53 | m: Dep[F, B], 54 | tagB: Tag[A] 55 | ): Make[F, A] = MakeOps.map(m.value)(contra.f) 56 | } 57 | 58 | trait LowPrioMake { 59 | implicit def debugInstance[F[_], A](implicit x: Debug[Make[F, A]]): Make[F, A] = x.v 60 | } 61 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/make/Tag.scala: -------------------------------------------------------------------------------- 1 | package make 2 | 3 | import scala.reflect.runtime.universe.TypeTag 4 | import scala.reflect.runtime.universe.WeakTypeTag 5 | import make.internal.SourcePosMacro 6 | import make.internal.TpeTagMacro 7 | 8 | final case class Tag[A]( 9 | typeTag: Tag.TpeTag[A], 10 | sourcePos: Tag.SourcePos, 11 | ) 12 | 13 | object Tag { 14 | 15 | def of[A](implicit tag: Tag[A]): Tag[A] = tag 16 | 17 | implicit def tagFor[A](implicit typeTag: TpeTag[A], sourcePos: SourcePos): Tag[A] = 18 | Tag(typeTag, sourcePos) 19 | 20 | final case class SourcePos(path: String, line: Int, start: Int) 21 | object SourcePos { 22 | implicit def materialize: SourcePos = 23 | macro SourcePosMacro.materializeSourcePos 24 | } 25 | 26 | final case class TpeTag[A](tpe: TpeTag.Type) 27 | 28 | final case class TPTag[A](tpe: TpeTag.Type) 29 | object TPTag { 30 | implicit def materialize[A]: TPTag[A] = 31 | macro TpeTagMacro.materializeTPTag[A] 32 | } 33 | final case class TCTag[F[_]](symbol: String) 34 | object TCTag { 35 | implicit def materialize[F[_]]: TCTag[F] = 36 | macro TpeTagMacro.materializeTCTag[F] 37 | } 38 | 39 | object TpeTag { 40 | 41 | def apply[A](implicit tag: TpeTag[A]): TpeTag[A] = tag 42 | 43 | final case class Type( 44 | symbol: String, 45 | arguments: List[Type] 46 | ) { 47 | 48 | override def toString: String = render 49 | 50 | def render: String = { 51 | val argsStr = arguments match { 52 | case Nil => "" 53 | case lst => lst.mkString("[", ", ", "]") 54 | } 55 | s"$symbol$argsStr" 56 | } 57 | } 58 | 59 | implicit def materialize[A]: TpeTag[A] = 60 | macro TpeTagMacro.materializeTpeTag[A] 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/make/annotated.scala: -------------------------------------------------------------------------------- 1 | package make 2 | 3 | object annotated { 4 | 5 | case class :@:[+A, B](value: A) 6 | 7 | implicit class Syntax[A](val a: A) extends AnyVal { 8 | def annotated[B]: A :@: B = :@:(a) 9 | } 10 | } -------------------------------------------------------------------------------- /modules/core/src/main/scala/make/autoMake.scala: -------------------------------------------------------------------------------- 1 | package make 2 | 3 | import scala.reflect.macros.blackbox 4 | import scala.annotation.StaticAnnotation 5 | import make.internal.MakeAnnotationMacro 6 | import scala.annotation.compileTimeOnly 7 | 8 | class autoMake extends StaticAnnotation { 9 | def macroTransform(annottees: Any*): Any = macro MakeAnnotationMacro.autoMake 10 | } 11 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/make/enableDebug.scala: -------------------------------------------------------------------------------- 1 | package make 2 | 3 | import make.internal.MakeMacro 4 | 5 | object enableDebug { 6 | implicit def makeToDebugSyntax(obj: Make.type): DebugSyntax = new DebugSyntax(obj) 7 | implicit def debugHook[F[_], A]: Debug[Make[F, A]] = macro MakeMacro.debugHook[F, A] 8 | } 9 | 10 | final class DebugSyntax(val obj: Make.type) extends AnyVal { 11 | def debugOf[F[_], A]: Make[F, A] = macro MakeMacro.debug[F, A] 12 | } 13 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/make/internal/DebugStMacro.scala: -------------------------------------------------------------------------------- 1 | package make.internal 2 | 3 | import scala.reflect.macros.whitebox 4 | import scala.collection.mutable 5 | 6 | abstract class DebugStMacro(val c: whitebox.Context) { 7 | 8 | val state = MacroState.getOrElseUpdate[DebugSt](c.universe, new DebugSt) 9 | 10 | sealed trait ResolveSt 11 | object ResolveSt { 12 | case object InProgress extends ResolveSt 13 | case class Resolved(tree: c.Tree) extends ResolveSt 14 | } 15 | 16 | class DebugSt( 17 | var debug: Boolean = false, 18 | val stack: mutable.ListBuffer[c.Type] = mutable.ListBuffer.empty, 19 | val resolveCache: mutable.HashMap[c.Type, ResolveSt] = mutable.HashMap.empty, 20 | val reverseTraces: mutable.HashMap[c.Type, List[c.Type]] = 21 | mutable.HashMap.empty 22 | ) { 23 | 24 | def registerFailedReason(targetTpe: c.Type, prev: c.Type): Unit = { 25 | val curr = reverseTraces.getOrElse(prev, List.empty) 26 | val next = targetTpe :: curr 27 | reverseTraces.update(prev, next) 28 | } 29 | 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/make/internal/DebugStateMacro.scala: -------------------------------------------------------------------------------- 1 | package make.internal 2 | 3 | import scala.reflect.macros.whitebox 4 | import scala.collection.mutable 5 | 6 | abstract class DebugStateMacro(val c: whitebox.Context) { 7 | import c.universe._ 8 | 9 | val debugState = MacroState.getOrElseUpdate[DebugSt](c.universe, new DebugSt) 10 | 11 | sealed trait ResolveSt 12 | object ResolveSt { 13 | case object InProgress extends ResolveSt 14 | case class Resolved(tree: Tree) extends ResolveSt 15 | } 16 | 17 | class DebugSt( 18 | var debug: Boolean = false, 19 | val stack: mutable.ListBuffer[Type] = mutable.ListBuffer.empty, 20 | val resolveCache: mutable.HashMap[Type, ResolveSt] = mutable.HashMap.empty, 21 | val reverseTraces: mutable.HashMap[Type, List[Type]] = 22 | mutable.HashMap.empty 23 | ) { 24 | 25 | def registerFailedReason(targetTpe: Type, prev: Type): Unit = { 26 | val curr = reverseTraces.getOrElse(prev, List.empty) 27 | val next = targetTpe :: curr 28 | reverseTraces.update(prev, next) 29 | } 30 | 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/make/internal/DepMacro.scala: -------------------------------------------------------------------------------- 1 | package make.internal 2 | 3 | import scala.reflect.macros.whitebox 4 | import scala.collection.mutable 5 | import make.Dep 6 | import make.Make 7 | import scala.annotation.tailrec 8 | 9 | class DepMacro(val c: whitebox.Context) { 10 | 11 | import c.universe._ 12 | 13 | def materialize[F[_], A](implicit 14 | ftpe: WeakTypeTag[F[X] forSome { type X }], 15 | atpe: WeakTypeTag[A] 16 | ): c.Expr[Dep[F, A]] = { 17 | val (root, state) = getOrCreateState 18 | val out = state.stack.find(_ =:= atpe.tpe) match { 19 | case None => 20 | state.stack.append(atpe.tpe) 21 | state.cache.get(atpe.tpe).getOrElse(state.infer[F](ftpe.tpe, atpe.tpe)) match { 22 | case EmptyTree => 23 | state.cache.update(atpe.tpe, EmptyTree) 24 | c.abort(c.enclosingPosition, "failed") 25 | case tree => 26 | val last = state.stack.length 27 | val depTree = q"_root_.make.Dep[${ftpe.tpe}, ${atpe.tpe}]($tree)" 28 | 29 | state.stack.remove(last - 1) 30 | state.cache.update(atpe.tpe, tree) 31 | c.Expr[Dep[F, A]](depTree) 32 | } 33 | case Some(_) => 34 | state.cache.update(atpe.tpe, EmptyTree) 35 | c.abort(c.enclosingPosition, s"Cycle detected ${atpe.tpe}: ${state.stack.mkString("\n")}") 36 | } 37 | 38 | if (root) { 39 | c.internal.removeAttachment[State](c.macroApplication) 40 | 41 | val clsName = TypeName(c.freshName("Dep")) 42 | 43 | val named = state.cache.map{case (tpe, instance) => 44 | val newName = TermName(c.freshName("instance")) 45 | val depTc = c.universe.weakTypeOf[Make[F, _]].typeConstructor 46 | val fullTpe = c.universe.appliedType(depTc, ftpe.tpe, tpe) 47 | (tpe, (newName, instance)) 48 | } 49 | 50 | val (from, to) = named.map{case (_, (name, instance)) => 51 | (instance.symbol, name) 52 | }.unzip 53 | 54 | val substituted = named.map{case (tpe, (name, instance)) => 55 | val (from, to) = named.filter(_._2._1 != name) 56 | .map{case (tpe, (name, instance)) => 57 | val depTc = c.universe.weakTypeOf[Make[F, _]].typeConstructor 58 | val fullTpe = c.universe.appliedType(depTc, ftpe.tpe, tpe) 59 | ((instance.symbol, fullTpe), (name, fullTpe)) 60 | }.unzip 61 | val replaced = new TransformerX(clsName, from.toList, to.toList).transform(instance) 62 | val z = new StripUnApplyNodes().transform(c.untypecheck(replaced)) 63 | (tpe, (name, replaced)) 64 | } 65 | 66 | val instances = substituted.map{ case (tpe, (name, instance)) => 67 | q"def $name: _root_.make.Make[${ftpe.tpe}, $tpe] = $instance" 68 | } 69 | 70 | val primaryName = named(atpe.tpe)._1 71 | val tree = 72 | q""" 73 | final class $clsName { 74 | ..$instances 75 | } 76 | _root_.make.Dep.apply[${ftpe.tpe}, ${atpe.tpe}]((new $clsName).$primaryName) 77 | """ 78 | c.Expr[Dep[F, A]](tree) 79 | } else { 80 | out 81 | } 82 | } 83 | 84 | private def getOrCreateState: (Boolean, State) = { 85 | val existing = 86 | c.openMacros.find(c => c.internal.attachments(c.macroApplication).contains[State]) 87 | .flatMap(c => c.internal.attachments(c.macroApplication).get[State]) 88 | existing match { 89 | case None => 90 | val st = new State(mutable.ArrayBuffer.empty, mutable.HashMap.empty) 91 | c.internal.updateAttachment(c.macroApplication, st) 92 | (true, st) 93 | case Some(st) => 94 | (false, st) 95 | } 96 | } 97 | 98 | 99 | class State( 100 | val stack: mutable.ArrayBuffer[Type], 101 | val cache: mutable.HashMap[Type, Tree] 102 | ) { 103 | 104 | def infer[F[_]](ftpe: c.Type, atpe: c.Type): c.Tree = { 105 | val makeTc = c.universe.weakTypeOf[Make[F, _]].typeConstructor 106 | val searchType = c.universe.appliedType(makeTc, ftpe, atpe) 107 | c.inferImplicitValue(searchType) 108 | } 109 | } 110 | 111 | class TransformerX( 112 | className: TypeName, 113 | fromSymbols: List[(Symbol, Type)], 114 | toSymbols: List[(TermName, Type)], 115 | ) extends Transformer { 116 | 117 | @tailrec 118 | private def findReplacement( 119 | sym: Symbol, 120 | tpe: Type, 121 | from: List[(Symbol, Type)], 122 | to: List[(TermName, Type)] 123 | ): Option[(TermName, Type)] = { 124 | from match { 125 | case (head, otpe) :: tl => 126 | if (sym == head && tpe == otpe){ 127 | Some(to.head) 128 | } else findReplacement(sym, tpe, tl, to.tail) 129 | case Nil => None 130 | } 131 | } 132 | 133 | override def transform(tree: c.universe.Tree): c.universe.Tree = { 134 | tree match { 135 | case v @ Apply(tA @ TypeApply(fn, params), other) => 136 | findReplacement(fn.symbol, tA.tpe.finalResultType, fromSymbols, toSymbols) match { 137 | case Some((sym, tpe)) => 138 | Select(This(className), sym) 139 | case None => 140 | Apply(TypeApply(transform(fn), transformTrees(params)), transformTrees(other)) 141 | } 142 | case a @ Apply(fn, params) => 143 | findReplacement(fn.symbol, a.tpe.finalResultType, fromSymbols, toSymbols) match { 144 | case Some((sym, tpe)) => 145 | Select(This(className), sym) 146 | case None => 147 | Apply(transform(fn), transformTrees(params)) 148 | } 149 | case x => x 150 | } 151 | } 152 | 153 | } 154 | 155 | class StripUnApplyNodes extends Transformer { 156 | val global = c.universe.asInstanceOf[scala.tools.nsc.Global] 157 | import global.nme 158 | 159 | override def transform(tree: Tree): Tree = { 160 | super.transform { 161 | tree match { 162 | case UnApply(Apply(Select(qual, nme.unapply | nme.unapplySeq), List(Ident(nme.SELECTOR_DUMMY))), args) => 163 | Apply(transform(qual), transformTrees(args)) 164 | case UnApply(Apply(TypeApply(Select(qual, nme.unapply | nme.unapplySeq), _), List(Ident(nme.SELECTOR_DUMMY))), args) => 165 | Apply(transform(qual), transformTrees(args)) 166 | case t => t 167 | } 168 | } 169 | } 170 | } 171 | 172 | } 173 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/make/internal/MacroState.scala: -------------------------------------------------------------------------------- 1 | package make.internal 2 | 3 | import scala.reflect.{ClassTag, classTag} 4 | import scala.reflect.macros.Universe 5 | 6 | /** 7 | * Taken from shapeless - https://github.com/milessabin/shapeless/blob/master/core/src/main/scala/shapeless/MacroState.scala 8 | */ 9 | private[make] object MacroState { 10 | private class MacroStateAttachment(val state: collection.mutable.HashMap[Class[_], Any]) 11 | 12 | /** 13 | * Associates some state with a global.Run. Preferable to using static state in the macro 14 | * classloader (e.g vars in top level objects) so as to avoid race conditions under `-Ycache-macro-classloader`. 15 | * 16 | * @tparam T the type of the state. The erased `Class[_]` value for this type will be used a a map key, so each user of 17 | * this facility should use a dedicated and distinct class to wrap the state. 18 | * @param u The reflection universe, typically obtained from `context.universe` in a macro implementation. 19 | * @param factory Factory to create the state. 20 | */ 21 | def getOrElseUpdate[T: ClassTag](u: Universe, factory: => T): T = { 22 | // Cast needed for access to perRunCaches and convenient access to attachments 23 | val g = u.asInstanceOf[scala.reflect.internal.SymbolTable] 24 | 25 | // Sneakily use a symbol attachment on a well-known symbol to hold our map of macro states. 26 | // Compiler plugins would typically use `val someState = global.perRunCaches.newMap` in a `Component` that 27 | // is instantiated once per Global, but macros don't have an analagous place. 28 | // 29 | // An alternative would be a `Map[Universe, MacroState]`, but this would need to be carefully constructed 30 | // with weak references to avoid leaking the `Universe` through the key or values. 31 | val holderSymbol = g.definitions.AnyClass 32 | 33 | // The erasure of `T` is the key for our internal map 34 | val cls = classTag[T].runtimeClass 35 | 36 | // classOf[MacroStateAttachment] is the key for the attachment lookup. 37 | holderSymbol.attachments.get[MacroStateAttachment] match { 38 | case Some(existing) => 39 | existing.state.getOrElseUpdate(cls, factory).asInstanceOf[T] 40 | case None => 41 | val value = factory 42 | // Use perRunCaches.newMap to clear this map before the next Run starts. 43 | // The REPL or presentation compiler use a single Global to perform multiple compilation Runs. 44 | val macroState = new MacroStateAttachment(g.perRunCaches.newMap()) 45 | macroState.state.put(cls, value) 46 | holderSymbol.updateAttachment(macroState) 47 | value 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/make/internal/MakeAnnotationMacro.scala: -------------------------------------------------------------------------------- 1 | package make.internal 2 | 3 | import scala.reflect.macros.blackbox 4 | import scala.reflect.internal.Flags 5 | import scala.annotation.tailrec 6 | 7 | class MakeAnnotationMacro(val c: blackbox.Context) { 8 | 9 | import c.universe._ 10 | 11 | def autoMake(annottees: Tree*): Tree = { 12 | annottees match { 13 | case List(cls: ClassDef) => 14 | Clz.extract(cls) match { 15 | case Some(clz) => 16 | q""" 17 | $cls 18 | object ${cls.name.toTermName} { 19 | ${instanceTree(clz)} 20 | } 21 | """ 22 | case None => reportUnexpectedError() 23 | } 24 | case List( 25 | cls: ClassDef, 26 | q"..$mods object $objName extends { ..$objEarlyDefs } with ..$objParents { $objSelf => ..$objDefs }" 27 | ) => 28 | Clz.extract(cls) match { 29 | case Some(clz) => 30 | q""" 31 | $cls 32 | $mods object $objName extends { ..$objEarlyDefs } with ..$objParents { $objSelf => 33 | ..$objDefs 34 | ..${instanceTree(clz)} 35 | } 36 | """ 37 | case None => reportUnexpectedError() 38 | } 39 | case _ => 40 | c.abort(c.enclosingPosition, "@deriveMake can be applied only on case classes or classes") 41 | } 42 | } 43 | 44 | private def reportUnexpectedError(): Nothing = 45 | c.abort(c.enclosingPosition, "Something went wrong. Please file an issue") 46 | 47 | private def instanceTree(clz: Clz): Tree = { 48 | import clz._ 49 | 50 | val effTpe = TermName(c.freshName("E")).toTypeName 51 | val paramsTpe = params.map(_.tpt) 52 | 53 | val dependencies: List[(TermName, c.Tree)] = params.zipWithIndex.map{case (dp, i) => 54 | val name = TermName(c.freshName(s"dep$i")) 55 | val tree = q"$name: _root_.make.Dep[$effTpe, ${dp.tpt}]" 56 | (name, tree) 57 | } 58 | val implicitDependencies = dependencies.map(_._2) 59 | 60 | val targetTpe = 61 | if (typeParams.isEmpty) 62 | tq"${name.toTypeName}" 63 | else 64 | tq"${name.toTypeName}[..${typeParams.map(_.name)}]" 65 | 66 | val impl = 67 | if (dependencies.isEmpty) { 68 | q"_root_.make.Make.pure[$effTpe, ${targetTpe}]($create)" 69 | } else { 70 | dependencies.reverse.map(_._1).foldLeft(EmptyTree){ 71 | case (EmptyTree, name) => q"_root_.make.internal.MakeOps.map(${name}.value)($create)" 72 | case (tree, name) => q"_root_.make.internal.MakeOps.ap(${name}.value)($tree)" 73 | } 74 | } 75 | 76 | val implicits = 77 | q"${TermName(c.freshName())}: _root_.cats.Applicative[$effTpe]" :: 78 | implicitDependencies ++ 79 | implicitParams.toList ++ 80 | typeParams.zipWithIndex.flatMap{ case (t, i) => 81 | tagFor(t).map{tagTpe => 82 | val name = TermName(c.freshName(s"tpeTag$i")) 83 | q"$name: $tagTpe" 84 | } 85 | } ++ 86 | (if (paramsTpe.isEmpty) List.empty else List(q"tag: _root_.make.Tag[$targetTpe]")) 87 | 88 | q""" 89 | implicit def make[$effTpe[_], ..$typeParams]( 90 | implicit ..${implicits} 91 | ): _root_.make.Make[$effTpe, $targetTpe] = 92 | $impl 93 | """ 94 | } 95 | 96 | case class DepParam(valDef: ValDef, tree: Tree, tpt: Tree) 97 | object DepParam { 98 | 99 | def create(v: ValDef, i: Int): DepParam = { 100 | val name = TermName(s"x$i") 101 | val (tree, tpt) = 102 | v.mods.annotations match { 103 | case anno :: _ => 104 | val annoTpe = annotationTpe(anno) 105 | val tpt = tq"_root_.make.annotated.:@:[${v.tpt}, $annoTpe]" 106 | val tree = Select(Ident(name), TermName("value")) 107 | (tree, tpt) 108 | case Nil => 109 | (Ident(name), v.tpt) 110 | } 111 | val valDef = ValDef(Modifiers(Flag.PARAM), TermName(s"x$i"), tpt, EmptyTree) 112 | DepParam(valDef, tree, tpt) 113 | } 114 | 115 | 116 | private def annotationTpe(tree: c.Tree): c.Tree = { 117 | tree match { 118 | case Apply(Select(New(annoSelect), _), _) => 119 | annoSelect 120 | case _ => 121 | c.abort(c.enclosingPosition, "Annotation decontruction failed") 122 | } 123 | } 124 | } 125 | 126 | // TODO 127 | private def tagFor(typeDef: TypeDef): Option[Tree] = { 128 | typeDef.tparams.size match { 129 | case 0 => None 130 | case 1 => Some(tq"_root_.make.Tag.TCTag[${typeDef.name}]") 131 | case _ => None 132 | } 133 | } 134 | 135 | case class Clz( 136 | name: TypeName, 137 | typeParams: List[TypeDef], 138 | params: List[DepParam], 139 | implicitParams: List[ValDef], 140 | create: Tree, 141 | clzSymbol: Symbol 142 | ) 143 | 144 | object Clz { 145 | 146 | def extract(clsDef: ClassDef): Option[Clz] = { 147 | findInit(clsDef.impl.body).map { init => 148 | val tparams = clsDef.tparams 149 | val (params, implicitParams) = 150 | init.vparamss.flatten.foldLeft((Vector.empty[ValDef], Vector.empty[ValDef])) { 151 | case ((pAcc, ipAcc), vdef) => 152 | val isImplicit = vdef.mods.hasFlag(Flag.IMPLICIT) 153 | if (isImplicit) 154 | (pAcc, ipAcc :+ vdef) 155 | else 156 | (pAcc :+ vdef, ipAcc) 157 | } 158 | 159 | val depParams = 160 | params.zipWithIndex 161 | .map({ case (d, i) => 162 | DepParam.create(d, i) 163 | }) 164 | .toList 165 | val create = createFunction(depParams, clsDef, init) 166 | 167 | Clz(clsDef.name, tparams, depParams, implicitParams.toList, create, clsDef.symbol) 168 | } 169 | } 170 | 171 | def createFunction( 172 | params: List[DepParam], 173 | clsDef: ClassDef, 174 | init: DefDef 175 | ): Tree = { 176 | 177 | @tailrec 178 | def toLambda(in: List[DepParam], acc: Tree): Tree = { 179 | in match { 180 | case head :: tl => 181 | val nextAcc = Function(List(head.valDef), acc) 182 | toLambda(tl, nextAcc) 183 | case Nil => acc 184 | } 185 | } 186 | 187 | val initAcc = 188 | Apply( 189 | Select(New(Ident(clsDef.name.decodedName)), init.name), 190 | params.map(d => d.tree) 191 | ) 192 | 193 | toLambda(params, initAcc) 194 | } 195 | 196 | def findInit(body: List[Tree]): Option[DefDef] = 197 | body.collectFirst { 198 | case defdef: DefDef if defdef.name.decodedName.toString == "" => defdef 199 | } 200 | 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/make/internal/MakeBasicOps.scala: -------------------------------------------------------------------------------- 1 | package make.internal 2 | 3 | import make.Tag 4 | import make.Make 5 | import cats.Applicative 6 | 7 | trait MakeBasicOps { 8 | 9 | def map[F[_]: Applicative, A, B: Tag](ma: Make[F, A])(f: A => B): Make[F, B] = 10 | Make.Bind( 11 | ma, 12 | (a: A) => Applicative[F].pure(f(a)), 13 | Tag.of[B] 14 | ) 15 | 16 | def mapF[F[_], A, B: Tag](ma: Make[F, A])(f: A => F[B]): Make[F, B] = 17 | Make.Bind( 18 | ma, 19 | (a: A) => f(a), 20 | Tag.of[B] 21 | ) 22 | 23 | def ap[F[_], A, B: Tag](ma: Make[F, A])(mf: Make[F, A => B]): Make[F, B] = 24 | Make.Ap( 25 | ma, 26 | mf, 27 | Tag.of[B] 28 | ) 29 | 30 | } 31 | 32 | object MakeBasicOps extends MakeBasicOps 33 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/make/internal/MakeMacro.scala: -------------------------------------------------------------------------------- 1 | package make.internal 2 | 3 | import scala.reflect.macros.whitebox 4 | import make.Make 5 | import make.Debug 6 | import scala.collection.mutable 7 | 8 | class MakeMacro(val ctx: whitebox.Context) extends DebugStateMacro(ctx) { 9 | 10 | import c.universe._ 11 | 12 | def debug[F[_], A](implicit 13 | ftpe: WeakTypeTag[F[X] forSome { type X }], 14 | atpe: WeakTypeTag[A] 15 | ): c.Expr[Make[F, A]] = { 16 | 17 | val makeTc = weakTypeOf[Make[F, _]].typeConstructor 18 | val searchType = appliedType(makeTc, ftpe.tpe, atpe.tpe) 19 | 20 | debugState.debug = true 21 | val out = c.inferImplicitValue(searchType) 22 | debugState.debug = false 23 | out match { 24 | case EmptyTree => 25 | val st = extractInstanceSt(atpe.tpe, debugState.reverseTraces) 26 | val message = renderInstanceSt(st) 27 | c.abort(c.enclosingPosition, s"Make for ${atpe.tpe} not found\n" + message) 28 | case tree => 29 | val message = s"Debug: OK!\n\tMake instance for ${atpe.tpe} exists.\n\nRemove debug usage." 30 | c.info(c.enclosingPosition, message, true) 31 | c.Expr[Make[F, A]](tree) 32 | } 33 | } 34 | 35 | def debugHook[F[_], A](implicit 36 | ftpe: WeakTypeTag[F[X] forSome { type X }], 37 | atpe: WeakTypeTag[A] 38 | ): c.Expr[Debug[Make[F, A]]] = { 39 | 40 | if (!debugState.debug) c.abort(c.enclosingPosition, "debug is not enabled") 41 | debugState.resolveCache.get(atpe.tpe) match { 42 | case Some(ResolveSt.InProgress) => c.abort(c.enclosingPosition, "skip") 43 | case Some(ResolveSt.Resolved(tree)) => c.abort(c.enclosingPosition, "skip") 44 | case None => 45 | val makeTc = weakTypeOf[Make[F, _]].typeConstructor 46 | val makeTpe = appliedType(makeTc, ftpe.tpe, atpe.tpe) 47 | debugState.stack.append(atpe.tpe) 48 | val pos = debugState.stack.size 49 | debugState.resolveCache.update(atpe.tpe, ResolveSt.InProgress) 50 | val tree = c.inferImplicitValue(makeTpe) 51 | debugState.resolveCache.update(atpe.tpe, ResolveSt.Resolved(tree)) 52 | debugState.stack.remove(pos - 1) 53 | tree match { 54 | case EmptyTree => 55 | debugState.stack.lastOption.foreach(debugState.registerFailedReason(atpe.tpe, _)) 56 | } 57 | c.abort(c.enclosingPosition, "skip") 58 | case _ => c.abort(c.enclosingPosition, "skip") 59 | } 60 | } 61 | 62 | private def extractInstanceSt( 63 | targetType: c.Type, 64 | reverseTraces: mutable.HashMap[Type, List[Type]] 65 | ): InstanceSt = { 66 | reverseTraces.get(targetType) match { 67 | case None => InstanceSt.NoInstances(targetType) 68 | case Some(paths) => 69 | val traces = paths.map { tpe => 70 | val depSt = extractInstanceSt(tpe, reverseTraces) 71 | InstanceSt.FailedTrace(tpe, depSt) 72 | } 73 | InstanceSt.FailedTraces(targetType, traces.toList) 74 | } 75 | } 76 | 77 | private def renderInstanceSt(st: InstanceSt): String = { 78 | 79 | def render(sb: StringBuilder, level: Int, st: InstanceSt): StringBuilder = { 80 | val ident = " " * level 81 | val appendIdent = ident + " " 82 | sb.append(s"\n${ident}${st.tpe}:") 83 | st match { 84 | case InstanceSt.NoInstances(_) => 85 | sb.append(s"\n${appendIdent}${st.tpe} not found") 86 | case InstanceSt.FailedTraces(_, traces) => 87 | traces.foldLeft(sb) { case (sb, trace) => 88 | render(sb, level + 1, trace.dependencySt) 89 | } 90 | } 91 | } 92 | 93 | render(new StringBuilder, 1, st).toString 94 | } 95 | 96 | sealed trait InstanceSt { self => 97 | def tpe: Type 98 | def lastTypes: List[Type] = { 99 | 100 | def extract(acc: List[Type], v: InstanceSt): List[Type] = { 101 | v match { 102 | case InstanceSt.NoInstances(tpe) => tpe :: acc 103 | case InstanceSt.FailedTraces(_, traces) => 104 | traces.foldLeft(acc){ case (a, st) => extract(a, st.dependencySt)} 105 | } 106 | } 107 | extract(List.empty, self) 108 | } 109 | } 110 | object InstanceSt { 111 | case class NoInstances(tpe: Type) extends InstanceSt 112 | 113 | case class FailedTrace( 114 | dependencyTpe: Type, 115 | dependencySt: InstanceSt 116 | ) 117 | 118 | case class FailedTraces(tpe: Type, traces: List[FailedTrace]) extends InstanceSt 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/make/internal/MakeOps.scala: -------------------------------------------------------------------------------- 1 | package make.internal 2 | 3 | trait MakeOps extends MakeProductNOps with MakeBasicOps 4 | object MakeOps extends MakeOps 5 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/make/internal/SourcePosMacro.scala: -------------------------------------------------------------------------------- 1 | package make.internal 2 | 3 | import scala.reflect.macros.blackbox 4 | import make.Tag.SourcePos 5 | 6 | class SourcePosMacro(val c: blackbox.Context) { 7 | 8 | import c.universe._ 9 | 10 | def materializeSourcePos: c.Expr[SourcePos] = { 11 | val owner = c.internal.enclosingOwner 12 | val pos = c.enclosingPosition 13 | val line = pos.line 14 | val start = pos.column 15 | val tree = q"new _root_.make.Tag.SourcePos(${owner.fullName}, $line, $start)" 16 | c.Expr[SourcePos](tree) 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/make/internal/Tarjans.scala: -------------------------------------------------------------------------------- 1 | package make.internal 2 | 3 | import scala.collection.mutable 4 | 5 | // Taken from https://github.com/lihaoyi/mill/blob/master/main/core/src/eval/Tarjans.scala 6 | object Tarjans { 7 | def apply(graph0: TraversableOnce[TraversableOnce[Int]]): Seq[Seq[Int]] = { 8 | val graph = graph0.map(_.toArray).toArray 9 | val n = graph.length 10 | val visited = new Array[Boolean](n) 11 | val stack = mutable.ArrayBuffer.empty[Integer] 12 | var time = 0 13 | val lowlink = new Array[Int](n) 14 | val components = mutable.ArrayBuffer.empty[Seq[Int]] 15 | 16 | for (u <- 0 until n) { 17 | if (!visited(u)) dfs(u) 18 | } 19 | 20 | def dfs(u: Int): Unit = { 21 | lowlink(u) = time 22 | time += 1 23 | visited(u) = true 24 | stack.append(u) 25 | var isComponentRoot = true 26 | for (v <- graph(u)) { 27 | if (!visited(v)) dfs(v) 28 | if (lowlink(u) > lowlink(v)) { 29 | lowlink(u) = lowlink(v) 30 | isComponentRoot = false 31 | } 32 | } 33 | if (isComponentRoot) { 34 | val component = mutable.Buffer.empty[Int] 35 | 36 | var done = false 37 | while (!done) { 38 | val x = stack.last 39 | stack.remove(stack.length - 1) 40 | component.append(x) 41 | lowlink(x) = Integer.MAX_VALUE 42 | if (x == u) done = true 43 | } 44 | components.append(component.toSeq) 45 | } 46 | } 47 | components.toSeq 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /modules/core/src/main/scala/make/internal/TpeTagMacro.scala: -------------------------------------------------------------------------------- 1 | package make.internal 2 | 3 | import scala.reflect.macros.blackbox 4 | import make.Tag.TpeTag 5 | import make.Tag 6 | 7 | class TpeTagMacro(val c: blackbox.Context) { 8 | 9 | import c.universe._ 10 | 11 | def materializeTpeTag[A : WeakTypeTag]: c.Expr[TpeTag[A]] = { 12 | val tpe = weakTypeOf[A] 13 | val value = transformTpe3(tpe) 14 | val tree = q"_root_.make.Tag.TpeTag[${tpe}]($value)" 15 | c.Expr[TpeTag[A]](tree) 16 | } 17 | 18 | private def transformTpe3(t: c.Type): c.Tree = { 19 | if (t.typeSymbol.isParameter) 20 | transformTpeParameter(t) 21 | else 22 | tranformDefinedTpe(t) 23 | } 24 | 25 | private def tranformDefinedTpe(t: c.Type): c.Tree = { 26 | val name = t.typeSymbol.fullName 27 | val nameTree = q"$name" 28 | processTypeParameters(nameTree, t) 29 | } 30 | 31 | private def processTypeParameters(symbolName: c.Tree, t: c.Type): c.Tree = { 32 | val inner = t.dealias.typeArgs.map(transformTpe3) 33 | q"_root_.make.Tag.TpeTag.Type($symbolName, $inner)" 34 | } 35 | 36 | private def transformTpeParameter(t: c.Type): c.Tree = { 37 | t.etaExpand match { 38 | case tpe: PolyType => 39 | tpe.typeParams.size match { 40 | case 1 => processTypeParameters(searchTCTag(t), t) 41 | case n => c.abort(c.enclosingPosition, s"Not implemented for type paramters where n=$n") 42 | } 43 | case _ => searchTPTag(t) 44 | } 45 | } 46 | 47 | 48 | private def searchTCTag(t: c.Type): c.Tree = { 49 | val tcTagTpe = appliedType(weakTypeOf[Tag.TCTag[X] forSome {type X[_]}].typeConstructor, t.typeConstructor) 50 | optionFromImplicitTree(c.inferImplicitValue(tcTagTpe)) 51 | .map(t => q"$t.symbol") 52 | .getOrElse(c.abort(c.enclosingPosition, "Not implemented")) 53 | } 54 | 55 | private def searchTPTag(t: c.Type): c.Tree = { 56 | val tagTpe = appliedType(weakTypeOf[Tag.TPTag[X] forSome {type X}].typeConstructor, t) 57 | optionFromImplicitTree(c.inferImplicitValue(tagTpe)) 58 | .map{t => q"$t.tpe"} 59 | .getOrElse(c.abort(c.enclosingPosition, "Not implemented")) 60 | } 61 | 62 | private def optionFromImplicitTree(tree: c.Tree): Option[c.Tree] = 63 | tree match { 64 | case EmptyTree => None 65 | case tree => Some(tree) 66 | } 67 | 68 | def materializeTCTag[F[_]](implicit 69 | weakTypeTag: WeakTypeTag[F[X] forSome {type X}] 70 | ): c.Expr[Tag.TCTag[F]] = { 71 | 72 | val tpe = weakTypeTag.tpe 73 | tpe.etaExpand 74 | tpe match { 75 | case tpe: PolyType => 76 | val symbol = c.internal.fullyInitialize(tpe.resultType.typeSymbol) 77 | val name = fullName(symbol) 78 | val tree = q"""_root_.make.Tag.TCTag[${weakTypeTag.tpe}](${name})""" 79 | c.Expr[Tag.TCTag[F]](tree) 80 | case ref: TypeRef => 81 | if (ref.sym.isParameter) { 82 | c.abort(c.enclosingPosition, "Failed to make TCTag") 83 | } else { 84 | val symbol = ref.sym 85 | val name = fullName(symbol) 86 | val tree = q"""_root_.make.Tag.TCTag[${weakTypeTag.tpe}](${name})""" 87 | c.Expr[Tag.TCTag[F]](tree) 88 | } 89 | case x => 90 | c.warning(c.enclosingPosition, s"Failed to create Tag.TCTag for $tpe") 91 | c.abort(c.enclosingPosition, "Failed to make TCTag") 92 | } 93 | } 94 | 95 | def materializeTPTag[A](implicit 96 | weakTypeTag: WeakTypeTag[A] 97 | ): c.Expr[Tag.TPTag[A]] = { 98 | val tpe = weakTypeTag.tpe.dealias.etaExpand 99 | val symbol = tpe.typeSymbol 100 | if (symbol.isParameter) { 101 | c.abort(c.enclosingPosition, s"Failed to make TPTag for $tpe") 102 | } else { 103 | val tree = q"""_root_.make.Tag.TPTag[${weakTypeTag.tpe}](_root_.make.Tag.TpeTag.Type(${symbol.fullName}, List.empty))""" 104 | c.Expr[Tag.TPTag[A]](tree) 105 | } 106 | } 107 | 108 | private def fullName(s: c.Symbol): String = { 109 | def loop(sym: c.Symbol, acc: List[String]): String = { 110 | val name = sym.name.decodedName.toString 111 | if (sym == NoSymbol || name == "") acc.mkString(".") 112 | else { 113 | // see test case with cats.Id 114 | val skip = 115 | name.startsWith(" B)(implicit F: Applicative[F], tagA: Tag[A]): Make[F, B] = 15 | MakeOps.map(m)(f) 16 | 17 | def mapF[B: Tag](f: A => F[B])(implicit tag: Tag[A]): Make[F, B] = 18 | MakeOps.mapF(m)(f) 19 | 20 | def ap[B: Tag](mf: Make[F, A => B])(implicit tag: Tag[A]): Make[F, B] = 21 | MakeOps.ap(m)(mf) 22 | 23 | def toGraph(implicit F: Monad[F]): Graph[F, A] = 24 | Graph.fromMake(m) 25 | 26 | def make(implicit F: Monad[F]): F[A] = 27 | toGraph.initEff 28 | } 29 | -------------------------------------------------------------------------------- /modules/core/src/test/java/anno/Sample.java: -------------------------------------------------------------------------------- 1 | package anno; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.Target; 5 | 6 | 7 | import static java.lang.annotation.ElementType.PARAMETER; 8 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 9 | 10 | @Target(PARAMETER) 11 | @Retention(RUNTIME) 12 | public @interface Sample {} -------------------------------------------------------------------------------- /modules/core/src/test/scala/make/MakeTest.scala: -------------------------------------------------------------------------------- 1 | package make 2 | 3 | import munit.FunSuite 4 | import cats.effect.IO 5 | import make.Tag.SourcePos 6 | 7 | import cats.implicits._ 8 | import make.syntax._ 9 | import cats.Applicative 10 | import scala.util.Random 11 | import make.Tag.TpeTag 12 | import make.syntax._ 13 | import scala.concurrent.ExecutionContext 14 | import make.internal.MakeOps 15 | import shapeless.test.illTyped 16 | 17 | class MakeTest extends FunSuite { 18 | 19 | test("annotated") { 20 | @autoMake 21 | class Anno(@anno.Sample a: Int) 22 | 23 | import make.annotated._ 24 | implicit val a = Make.pure[IO, Int :@: anno.Sample](42.annotated[anno.Sample]) 25 | Make.of[IO, Anno] 26 | } 27 | 28 | test("zero parameters autoMake") { 29 | @autoMake 30 | class ZeroArg { 31 | def hello: String = "hello" 32 | } 33 | val out = Make.of[IO, ZeroArg].make.unsafeRunSync.hello 34 | assertEquals(out, "hello") 35 | } 36 | 37 | test("instantiate once") { 38 | 39 | case class Y[F[_]](value: String) 40 | case class Z[F[_]](value: String) 41 | 42 | case class X[G[_]](z: Z[G], y: Y[G]) 43 | case class W[F[_]](y: Y[F], x: X[F]) 44 | 45 | @autoMake 46 | case class Foo(w: W[IO], z: Z[IO]) 47 | 48 | val rnd = new Random() 49 | 50 | implicit def yMake[F[_]: Applicative: Tag.TCTag]: Make[F, Y[F]] = 51 | Make.pure(Y(rnd.nextString(20))) 52 | 53 | implicit def zMake[F[_]: Applicative: Tag.TCTag]: Make[F, Z[F]] = 54 | Make.pure(Z(rnd.nextString(20))) 55 | 56 | implicit def xMake[G[_]: Applicative: Tag.TCTag](implicit deps: Make[G, (Z[G], Y[G])]): Make[G, X[G]] = 57 | deps.mapN((z, y) => X(z, y)) 58 | 59 | implicit def wMake[F[_]: Applicative: Tag.TCTag](implicit deps: Make[F, (Y[F], X[F])]): Make[F, W[F]] = 60 | deps.mapN((y, x) => W(y, x)) 61 | 62 | val resolve = Make.of[IO, (W[IO], X[IO], Foo)] 63 | 64 | val (w, x, foo) = resolve.make.unsafeRunSync() 65 | 66 | assert(w.x == x) 67 | assert(foo.w == w) 68 | assert(foo.z == w.x.z) 69 | } 70 | 71 | test("higher kind tags 1") { 72 | 73 | case class A[F[_]](value: F[String]) 74 | case class C[F[_]](a: A[F]) 75 | case class D[F[_], G[_]](b: C[F], c: C[G]) 76 | 77 | implicit def aMake[F[_]: Applicative, Z[_]: Applicative: Tag.TCTag]: Make[F, A[Z]] = Make.pure(A(Applicative[Z].pure("42"))) 78 | 79 | implicit def cMake[F[_]: Applicative, G[_]: Applicative: Tag.TCTag]( 80 | implicit a: Make[F, A[G]] 81 | ): Make[F, C[G]] = 82 | a.map(a => C(a)) 83 | 84 | implicit def dMake[F[_]: Applicative, G[_]: Applicative: Tag.TCTag, H[_]: Applicative: Tag.TCTag]( 85 | implicit deps: Make[F, (C[G], C[H])] 86 | ): Make[F, D[G, H]] = 87 | deps.mapN(D(_, _)) 88 | 89 | import enableDebug._ 90 | 91 | val resolved = Make.debugOf[IO, D[Option, IO]] 92 | val d = resolved.make.unsafeRunSync() 93 | assertEquals(d.b, C(A("42".some))) 94 | assertEquals(d.c.a.value.unsafeRunSync(), "42") 95 | } 96 | 97 | test("higher kind tags 2") { 98 | 99 | case class A[F[_]](value: F[String]) 100 | case class B(a: String) 101 | 102 | implicit def aMake[F[_]: Applicative, Z[_]: Applicative: Tag.TCTag]: Make[F, A[Z]] = Make.pure(A(Applicative[Z].pure("42"))) 103 | 104 | implicit def bMake[F[_]: Applicative: Tag.TCTag]( 105 | implicit a: Make[F, A[F]] 106 | ): Make[F, B] = 107 | a.mapF(a => a.value.map(v => B(v))) 108 | 109 | 110 | // import enableDebug._ 111 | 112 | val resolved = Make.of[IO, B] 113 | assertEquals(resolved.make.unsafeRunSync(), B("42")) 114 | } 115 | 116 | test("diverging implicit(tuples)") { 117 | 118 | case class A[F[_]](value: F[String]) 119 | case class B(a: String, i: Int) 120 | 121 | case class C(b: B) 122 | 123 | implicit def cMake[F[_]: Applicative]( 124 | implicit dep: Dep[F, B] 125 | ): Make[F, C] = dep.value.map(C(_)) 126 | 127 | implicit def bMake[F[_]: Applicative]( 128 | implicit 129 | deps: Dep[F, (Int, A[F])], 130 | ): Make[F, B] = 131 | deps.value.mapFN((i, a) => a.value.map(v => B(v, i))) 132 | 133 | import enableDebug._ 134 | 135 | implicit val aMake:Make[IO, A[IO]] = Make.pure[IO, A[IO]](A(IO("42"))) 136 | implicit val intMake = Make.pure[IO, Int](42) 137 | 138 | 139 | val resolved = Make.debugOf[IO, (C, B)] 140 | assertEquals(resolved.make.unsafeRunSync(), (C(B("42", 42)), B("42", 42))) 141 | } 142 | 143 | test("doesn't allow cycles") { 144 | illTyped( 145 | """ 146 | @autoMake 147 | case class A(b: B) 148 | @autoMake 149 | case class B(a: A) 150 | Make.of[IO, B] 151 | """ 152 | ) 153 | } 154 | 155 | } 156 | -------------------------------------------------------------------------------- /modules/core/src/test/scala/make/TagTest.scala: -------------------------------------------------------------------------------- 1 | package make 2 | 3 | import munit.FunSuite 4 | import cats.effect.IO 5 | import make.Tag.TpeTag 6 | 7 | class TagTest extends FunSuite { 8 | 9 | // test("tags") { 10 | // assertEquals(Tag.TpeTag[Int].tpe.render, "scala.Int") 11 | // assertEquals(Tag.TpeTag[List[String]].tpe.render, "scala.collection.immutable.List[java.lang.String]") 12 | // assertEquals(func[IO].tpe.render, "make.TagTest.ABC[cats.effect.IO]") 13 | // assertEquals(func2[IO].tpe.render, "cats.effect.IO[scala.Int]") 14 | // assertEquals(func3[String].tpe.render, "scala.collection.immutable.List[java.lang.String]") 15 | // assertEquals(func[cats.Id].tpe.render, "make.TagTest.ABC[cats.Id]") 16 | // assertEquals(func2[cats.Id].tpe.render, "cats.Id[scala.Int]") 17 | // assertEquals(func4[String, IO[List[Int]]].tpe.render, "scala.Tuple2[java.lang.String, cats.effect.IO[scala.collection.immutable.List[scala.Int]]]") 18 | // } 19 | 20 | // class ABC[F[_]] 21 | // def func[F[_]: Tag.TCTag]: Tag.TpeTag[ABC[F]] = Tag.TpeTag[ABC[F]] 22 | 23 | // def func2[G[_]: Tag.TCTag]: Tag.TpeTag[G[Int]] = Tag.TpeTag[G[Int]] 24 | // def func3[A: Tag.TPTag]: Tag.TpeTag[List[A]] = Tag.TpeTag[List[A]] 25 | // def func4[A, B](implicit tag: TpeTag[(A, B)]): Tag.TpeTag[(A, B)] = Tag.TpeTag[(A, B)] 26 | 27 | 28 | } 29 | -------------------------------------------------------------------------------- /modules/core/src/test/scala/make/itCompiles.scala: -------------------------------------------------------------------------------- 1 | package make 2 | 3 | import cats.effect.Resource 4 | import make.internal.MakeOps 5 | import cats.effect.IO 6 | import cats.effect.Sync 7 | 8 | /** 9 | * Not an actual test - just checks that code compiles 10 | */ 11 | object itCompiles { 12 | 13 | // object annotations { 14 | 15 | // @autoMake 16 | // case class Smth1(a: Int) 17 | // object Smth1Test { 18 | // implicit val intMake = Make.pure[IO, Int](42) 19 | // Make.of[IO, Smth1] 20 | // } 21 | 22 | // @autoMake 23 | // case class Smth2(a: Int, b: String) 24 | // object Smth2Test { 25 | // implicit val intMake = Make.pure[IO, Int](42) 26 | // implicit val stringMake = Make.pure[IO, String]("42") 27 | // Make.of[IO, Smth2] 28 | // } 29 | 30 | // @autoMake 31 | // case class Smth3(a: Int, b: String, c: Double) 32 | // object Smth3Test { 33 | // implicit val intMake = Make.pure[IO, Int](42) 34 | // implicit val stringMake = Make.pure[IO, String]("42") 35 | // implicit val doubleMake = Make.pure[IO, Double](42) 36 | // Make.of[IO, Smth3] 37 | // } 38 | 39 | // @autoMake 40 | // case class Smth2Implicit[F[_]](a: Int, b: String)(implicit val F: Sync[F]) 41 | // object Smth2ImplicitTest { 42 | // implicit val intMake = Make.pure[IO, Int](42) 43 | // implicit val stringMake = Make.pure[IO, String]("42") 44 | // Make.of[IO, Smth2Implicit[IO]] 45 | // } 46 | 47 | // @autoMake 48 | // class NonCase1(a: Int) 49 | // object NonCase1Test { 50 | // implicit val intMake = Make.pure[IO, Int](42) 51 | // Make.of[IO, NonCase1] 52 | // } 53 | 54 | // @autoMake 55 | // class NonCase2(a: Int, b: String) 56 | // object NonCase2Test { 57 | // implicit val intMake = Make.pure[IO, Int](42) 58 | // implicit val stringMake = Make.pure[IO, String]("42") 59 | // Make.of[IO, NonCase2] 60 | // } 61 | 62 | // @autoMake 63 | // class NonCaseImplicit[F[_]](a: Int, b: String)(implicit val F: Sync[F]) 64 | // object NonCaseImplicitTest { 65 | // implicit val intMake = Make.pure[IO, Int](42) 66 | // implicit val stringMake = Make.pure[IO, String]("42") 67 | // Make.of[IO, NonCaseImplicit[IO]] 68 | // } 69 | 70 | // @autoMake 71 | // class NonCaseImplicit2[F[_]: Sync](a: Int, b: String) 72 | // object NonCaseImplicit2Test { 73 | // implicit val intMake = Make.pure[IO, Int](42) 74 | // implicit val stringMake = Make.pure[IO, String]("42") 75 | // Make.of[IO, NonCaseImplicit2[IO]] 76 | // } 77 | 78 | // @autoMake 79 | // class HasTpeParam[X](a: X) 80 | // object HasTpeParam { 81 | // implicit val intMake = Make.pure[IO, Int](42) 82 | // Make.of[IO, HasTpeParam[Int]] 83 | // } 84 | // } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /modules/example/src/main/scala/example/ExampleCatsEffect.scala: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | import cats.implicits._ 4 | import cats.effect._ 5 | import cats.effect.implicits._ 6 | import cats.effect.IOApp 7 | 8 | import make._ 9 | import make.syntax._ 10 | import cats.Applicative 11 | // import make.ce.resource._ 12 | 13 | object ExampleCatsEffect extends IOApp { 14 | 15 | trait Dep { 16 | def v: String 17 | } 18 | 19 | @autoMake 20 | case class DepImpl(v: String) extends Dep 21 | 22 | @autoMake 23 | case class Hoho(dep: Dep) 24 | 25 | @autoMake 26 | case class Yohoho( 27 | dep1: Dep, 28 | hoho: Hoho 29 | ) 30 | 31 | @autoMake 32 | class Yohoho2( 33 | dep1: Dep, 34 | hoho: Hoho 35 | ) 36 | 37 | @autoMake 38 | class End(yo: Yohoho, yo2: Yohoho2) 39 | 40 | case class A(b: B) 41 | object A { 42 | implicit def make[F[_]: Applicative](implicit bM: Strict[Make[F, B]]): Make[F, A] = 43 | bM.value.map(new A(_)) 44 | } 45 | case class B(a: A) 46 | object B { 47 | implicit def make[F[_]: Applicative](implicit aM: Strict[Make[F, A]]): Make[F, B] = 48 | aM.value.map(new B(_)) 49 | } 50 | 51 | @autoMake 52 | case class Z(v: String, a: A, b: B) 53 | 54 | override def run(args: List[String]): IO[ExitCode] = { 55 | implicit val depImplAsDep = Make.widen[Dep, DepImpl] 56 | 57 | type InitEff[A] = Resource[IO, A] 58 | implicit val initString = Make.eff(Resource.pure[IO, String]("asdasd")) 59 | 60 | import enableDebug._ 61 | val make = Make.debugOf[InitEff, Z] 62 | 63 | for { 64 | _ <- make.make.use(end => IO(println(end))) 65 | } yield ExitCode.Success 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /modules/example/src/main/scala/example/ExampleZio.scala: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | import zio._ 4 | import zio.console._ 5 | import make._ 6 | import make.syntax._ 7 | 8 | import zio.interop.catz._ 9 | 10 | object ExampleZio extends App { 11 | 12 | trait Dep { 13 | def v: String 14 | } 15 | 16 | @autoMake 17 | case class DepImpl(v: String) extends Dep 18 | 19 | @autoMake 20 | case class Hoho(dep: Dep) 21 | 22 | @autoMake 23 | case class Yohoho( 24 | dep1: Dep, 25 | hoho: Hoho 26 | ) 27 | 28 | @autoMake 29 | class Yohoho2( 30 | dep1: Dep, 31 | hoho: Hoho 32 | ) 33 | 34 | @autoMake 35 | class End(yo: Yohoho, yo2: Yohoho2) 36 | 37 | override def run(args: List[String]): URIO[ZEnv, ExitCode] = { 38 | 39 | ??? 40 | // implicit val depImplAsDep = Make.widen[Dep, DepImpl] 41 | 42 | // implicit val initString = Make.eff[RManaged[Any, ?], String]( 43 | // ZManaged.effect("asd") 44 | // ) 45 | 46 | // import enableDebug._ 47 | // val value = Make.debugOf[RManaged[Any, ?], End] 48 | 49 | // for { 50 | // _ <- value.make.orDie.use(r => putStrLn(r.toString)) 51 | // } yield ExitCode.success 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /modules/example/src/main/scala/example/FromReadme.scala: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | import make._ 4 | import make.syntax._ 5 | import cats._ 6 | import cats.implicits._ 7 | import cats.effect._ 8 | 9 | object FromReadme extends IOApp { 10 | 11 | // // define instance 12 | // case class Foo(a: Int) 13 | // object Foo { 14 | // // just a pure instance 15 | // //implicit val make: Make[IO, Foo] = Make.pure(Foo(42)) 16 | // // or construct it in F[_] 17 | // //implicit val make: Make[IO, Foo] = Make.eff(IO(Foo(42))) 18 | // } 19 | 20 | // case class Bar(foo: Foo) 21 | // object Bar { 22 | // // define instance that is is build from dependency 23 | // implicit def make(implicit dep: Make[IO, Foo]): Make[IO, Bar] = 24 | // dep.map(foo => Bar(foo)) 25 | // } 26 | 27 | // case class Baz(foo: Foo, bar: Bar) 28 | // object Baz { 29 | // // use tuple for several depencies 30 | // implicit def make(implicit dep: Make[IO, (Foo, Bar)]): Make[IO, Baz] = 31 | // dep.mapN((foo, bar) => Baz(foo, bar)) 32 | // } 33 | 34 | // // or use @autoMake annotation to generate the code above 35 | // @autoMake 36 | // class AutoBaz(foo: Foo, bar: Bar) 37 | case class Foo(i: Int) 38 | object Foo { 39 | implicit val make: Make[IO, Foo] = Make.pure[IO, Int](1).map(i => Foo(i)) 40 | } 41 | 42 | @autoMake 43 | case class Bar(i: Int) 44 | 45 | implicit val intInstance: Make[IO, Int] = Make.pure(42) 46 | 47 | // // in this case `v` will be `Left(Conflicts)` 48 | // // because `Foo.make` adds an additional `Int` into resolution graph 49 | // val v: Either[Conflicts, IO[A]] = Make.of[IO, Bar].make 50 | 51 | override def run(args: List[String]): IO[ExitCode] = { 52 | val v = Make.of[IO, (Bar, Foo)].make 53 | println(v) 54 | ??? 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /project/Boilerplate.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | 3 | /** 4 | * Copied, with some modifications, from https://github.com/milessabin/shapeless/blob/master/project/Boilerplate.scala 5 | * 6 | * Generate a range of boilerplate classes, those offering alternatives with 0-22 params 7 | * and would be tedious to craft by hand 8 | */ 9 | object Boilerplate { 10 | 11 | import scala.StringContext._ 12 | 13 | implicit class BlockHelper(val sc: StringContext) extends AnyVal { 14 | def block(args: Any*): String = { 15 | val interpolated = sc.standardInterpolator(treatEscapes, args) 16 | val rawLines = interpolated split '\n' 17 | val trimmedLines = rawLines map { _ dropWhile (_.isWhitespace) } 18 | trimmedLines mkString "\n" 19 | } 20 | } 21 | 22 | 23 | val templates: Seq[Template] = List( 24 | MakeProductNOps, MakeTupleInstances, MakeTupleSyntaxClasses, MakeTupleSyntax, 25 | TupleOfMakeSyntaxClasses, TupleOfMakeSyntax 26 | ) 27 | 28 | /** Returns a seq of the generated files. As a side-effect, it actually generates them... */ 29 | def gen(dir : File) = for(t <- templates) yield { 30 | val tgtFile = dir / t.packageName / t.filename 31 | IO.write(tgtFile, t.body) 32 | tgtFile 33 | } 34 | 35 | /* 36 | Blocks in the templates below use a custom interpolator, combined with post-processing to produce the body 37 | 38 | - The contents of the `header` val is output first 39 | 40 | - Then the first block of lines beginning with '|' 41 | 42 | - Then the block of lines beginning with '-' is replicated once for each arity, 43 | with the `templateVals` already pre-populated with relevant relevant vals for that arity 44 | 45 | - Then the last block of lines prefixed with '|' 46 | 47 | The block otherwise behaves as a standard interpolated string with regards to variable substitution. 48 | */ 49 | object MakeProductNOps extends Template { 50 | override def packageName: String = "make/internal" 51 | override def filename: String = "MakeProductNOps.scala" 52 | override def range: Range = 2 to 22 53 | override def content(tv: TemplateVals): String = { 54 | 55 | import tv._ 56 | 57 | val apArgs = synVals.reverse.dropRight(1) 58 | val impl = synVals.foldLeft(""){ 59 | case ("" , c) => s"MakeBasicOps.map($c)(${synVals.mkString("", " => ", " => ")} ${`(a..n)`})" 60 | case (acc, c) => s"MakeBasicOps.ap($c)($acc)" 61 | } 62 | val toArity = arity - 1 63 | val funcTags = 64 | (1 to toArity).map(x => { 65 | val in = (x to toArity).map(n => (n+'A').toChar).map(_.toChar).mkString(" => ") 66 | s"$in => ${`(A..N)`}" 67 | }).map(s => s"Tag[$s]") 68 | 69 | val implictTags = 70 | (s"Tag[${synTypes.mkString("(",",", ")")}]" :: funcTags.toList) 71 | .zipWithIndex.map{case (t, i) => s"tag$i: $t"} 72 | .mkString(",") 73 | 74 | 75 | block""" 76 | |package make.internal 77 | | 78 | |import make.Make 79 | |import make.Tag 80 | |import cats.Applicative 81 | | 82 | |trait MakeProductNOps { 83 | | 84 | - def productN[FF[_]: Applicative, ${`A..N`}](${`MakeA..MakeN`})(implicit $implictTags): Make[FF, ${`(A..N)`}] = 85 | - $impl 86 | |} 87 | """ 88 | } 89 | } 90 | 91 | object MakeTupleInstances extends Template { 92 | override def packageName: String = "make" 93 | override def filename: String = "MakeTupleInstances.scala" 94 | override def range: Range = 2 to 22 95 | override def content(tv: TemplateVals): String = { 96 | 97 | import tv._ 98 | 99 | val toArity = arity - 1 100 | val funcTags = 101 | (1 to toArity).map(x => { 102 | val in = (x to toArity).map(n => (n+'A').toChar).map(_.toChar).mkString(" => ") 103 | s"$in => ${`(A..N)`}" 104 | }).map(s => s"Tag[$s]") 105 | 106 | val implicitTags = 107 | (s"Tag[${synTypes.mkString("(",",", ")")}]" :: funcTags.toList) 108 | .zipWithIndex.map{case (t, i) => s"tag$i: $t"} 109 | 110 | 111 | val deps = (0 until arity).map(n => { 112 | val tpe = (n+'A').toChar 113 | val arg = (n+'a').toChar 114 | s"$arg: Dep[FF, $tpe]" 115 | }) 116 | val args = synVals.map(s => s"$s.value").mkString(",") 117 | 118 | val implicitValues = (implicitTags ++ deps).mkString(",") 119 | 120 | 121 | block""" 122 | |package make 123 | | 124 | |import make.internal.MakeOps 125 | |import cats.Applicative 126 | | 127 | |trait MakeTupleInstances { 128 | | 129 | - implicit def tuple$arity[FF[_]: Applicative, ${`A..N`}](implicit $implicitValues): Make[FF, ${`(A..N)`}] = 130 | - MakeOps.productN($args) 131 | |} 132 | """ 133 | } 134 | } 135 | 136 | object MakeTupleSyntaxClasses extends Template { 137 | override def packageName: String = "make" 138 | override def filename: String = "tupleNSyntaxClasses.scala" 139 | override def range: Range = 2 to 22 140 | override def content(tv: TemplateVals): String = { 141 | 142 | import tv._ 143 | 144 | block""" 145 | |package make 146 | | 147 | |import make.internal.MakeOps 148 | |import cats.Applicative 149 | | 150 | |object tupleNSyntaxClasses { 151 | - class MakeTupleNSyntax$arity[FF[_], ${`A..N`}](private val v: Make[FF, ${`(A..N)`}]) extends AnyVal { 152 | - def mapN[Res: Tag](ff: ${`(A..N)`} => Res)(implicit FF: Applicative[FF]): Make[FF, Res] = 153 | - MakeOps.map(v)({case ${`(a..n)`} => ff${`(a..n)`}}) 154 | - 155 | - def mapFN[Res: Tag](ff: ${`(A..N)`} => FF[Res])(implicit FF: Applicative[FF]): Make[FF, Res] = 156 | - MakeOps.mapF(v)({case ${`(a..n)`} => ff${`(a..n)`}}) 157 | - } 158 | |} 159 | """ 160 | } 161 | } 162 | 163 | object MakeTupleSyntax extends Template { 164 | override def packageName: String = "make" 165 | override def filename: String = "MakeTupleSyntax.scala" 166 | override def range: Range = 2 to 22 167 | override def content(tv: TemplateVals): String = { 168 | 169 | import tv._ 170 | 171 | block""" 172 | |package make 173 | | 174 | |import make.internal.MakeOps 175 | |import cats.Applicative 176 | | 177 | |trait MakeTupleSyntax { 178 | | 179 | - implicit def makeToTupleNSyntax$arity[FF[_], ${`A..N`}](make: Make[FF, ${`(A..N)`}]) = 180 | - new tupleNSyntaxClasses.MakeTupleNSyntax$arity(make) 181 | - 182 | |} 183 | """ 184 | } 185 | } 186 | 187 | object TupleOfMakeSyntaxClasses extends Template { 188 | override def packageName: String = "make" 189 | override def filename: String = "tupleOfMakeSyntaxClasses.scala" 190 | override def range: Range = 2 to 22 191 | override def content(tv: TemplateVals): String = { 192 | 193 | import tv._ 194 | 195 | val mapFImpl = (1 to arity).foldLeft(""){ 196 | case ("" , c) => s"MakeOps.mapF(v._$c)(${synVals.mkString("", " => ", " => ")} ff${`(a..n)`})" 197 | case (acc, c) => s"MakeOps.ap(v._$c)($acc)" 198 | } 199 | val defMapN = { 200 | val mapImpl = (1 to arity).foldLeft(""){ 201 | case ("" , c) => s"MakeOps.map(v._$c)(${synVals.mkString("", " => ", " => ")} ff${`(a..n)`})" 202 | case (acc, c) => s"MakeOps.ap(v._$c)($acc)" 203 | } 204 | val toArity = arity - 1 205 | val funcTags = 206 | (1 to toArity).map(x => { 207 | val in = (x to toArity).map(n => (n+'A').toChar).map(_.toChar).mkString(" => ") 208 | s"$in => Res" 209 | }).map(s => s"Tag[$s]") 210 | 211 | 212 | val implicitTags = 213 | funcTags 214 | .toList 215 | .zipWithIndex.map{case (t, i) => s"tag$i: $t"} 216 | .mkString(",") 217 | s"""def mapN[Res: Tag](ff: ${`(A..N)`} => Res)(implicit FF: Applicative[FF], $implicitTags): Make[FF, Res] = $mapImpl""" 218 | } 219 | 220 | val defMapFN = { 221 | val mapToTuple = (1 to arity).foldLeft(""){ 222 | case ("" , c) => s"MakeOps.map(v._$c)(${synVals.map(c => s"($c: ${(c-32).toChar})").mkString("", " => ", " => ")} ${`(a..n)`})" 223 | case (acc, c) => s"MakeOps.ap(v._$c)($acc)" 224 | } 225 | val impl = s"MakeOps.mapF($mapToTuple)({case ${`(a..n)`} => ff${`(a..n)`}})" 226 | val toArity = arity - 1 227 | val funcTags = 228 | (1 to toArity).map(x => { 229 | val in = (x to toArity).map(n => (n+'A').toChar).map(_.toChar).mkString(" => ") 230 | s"$in => ${`(A..N)`}" 231 | }).map(s => s"Tag[$s]") 232 | .toList 233 | 234 | val implicitTags = 235 | (s"Tag[${`(A..N)`}]" :: funcTags) 236 | .zipWithIndex.map{case (t, i) => s"tag$i: $t"} 237 | .mkString(",") 238 | 239 | s"def mapFN[Res: Tag](ff: ${`(A..N)`} => FF[Res])(implicit FF: Applicative[FF], $implicitTags): Make[FF, Res] = $impl" 240 | } 241 | 242 | block""" 243 | |package make 244 | | 245 | |import make.internal.MakeOps 246 | |import cats.Applicative 247 | | 248 | |object tupleOfNMakeSyntaxClasses { 249 | - class TupleOfMakeNSyntax$arity[FF[_], ${`A..N`}](private val v: ${`(Make[F, A]..Make[F, N])`("FF")}) extends AnyVal { 250 | - $defMapN 251 | - $defMapFN 252 | - } 253 | |} 254 | """ 255 | } 256 | } 257 | 258 | object TupleOfMakeSyntax extends Template { 259 | override def packageName: String = "make" 260 | override def filename: String = "TupleOfMakeSyntax.scala" 261 | override def range: Range = 2 to 22 262 | override def content(tv: TemplateVals): String = { 263 | 264 | import tv._ 265 | 266 | block""" 267 | |package make 268 | | 269 | |import make.internal.MakeOps 270 | |import cats.Applicative 271 | | 272 | |trait TupleOfMakeSyntax { 273 | | 274 | - implicit def tupleOfMakeNSyntax$arity[FF[_], ${`A..N`}](tuple: ${`(Make[F, A]..Make[F, N])`("FF")}) = 275 | - new tupleOfNMakeSyntaxClasses.TupleOfMakeNSyntax$arity(tuple) 276 | - 277 | |} 278 | """ 279 | } 280 | } 281 | 282 | trait Template { self => 283 | 284 | def packageName: String 285 | 286 | def createVals(arity: Int): TemplateVals = new TemplateVals(arity) 287 | 288 | def filename: String 289 | def content(tv: TemplateVals): String 290 | def range: Range = 1 to 22 291 | def body: String = { 292 | val rawContents = range map { n => content(createVals(n)) split '\n' filterNot (_.isEmpty) } 293 | val preBody = rawContents.head takeWhile (_ startsWith "|") map (_.tail) 294 | val instances = rawContents flatMap {_ filter (_ startsWith "-") map (_.tail) } 295 | val postBody = rawContents.head dropWhile (_ startsWith "|") dropWhile (_ startsWith "-") map (_.tail) 296 | (preBody ++ instances ++ postBody) mkString "\n" 297 | } 298 | } 299 | 300 | class TemplateVals(val arity: Int) { 301 | val synTypes = (0 until arity) map (n => (n+'A').toChar) 302 | val synVals = (0 until arity) map (n => (n+'a').toChar) 303 | val synTypedVals = (synVals zip synTypes) map { case (v,t) => v + ":" + t} 304 | 305 | val `A..N` = synTypes.mkString(", ") 306 | val `MakeA..MakeN` = (synVals zip synTypes.map(t => s"Make[FF, $t]")) map { case (v, t) => s"$v: $t"} mkString(",") 307 | 308 | val `(A..N)` = if (arity == 1) "Tuple1[A]" else synTypes.mkString("(", ", ", ")") 309 | def `(Make[F, A]..Make[F, N])`(F: String): String = 310 | synTypes.map(t => s"Make[$F, $t]") mkString("(", ", ", ")") 311 | val `(a..n)` = if (arity == 1) "Tuple1(a)" else synVals.mkString("(", ", ", ")") 312 | } 313 | 314 | } -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.3.9 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.0") 2 | addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.9.4") 3 | addSbtPlugin("com.jsuereth" % "sbt-pgp" % "2.0.1") 4 | --------------------------------------------------------------------------------