aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJon Pretty <jon.pretty@propensive.com>2017-05-24 20:41:10 +0100
committerJon Pretty <jon.pretty@propensive.com>2017-05-24 20:41:10 +0100
commitb039a653e9e90530a76aef42df9215c151c65b67 (patch)
treeb925339332ed2824bb4a4910800fb0e923719c1a
downloadmagnolia-b039a653e9e90530a76aef42df9215c151c65b67.tar.gz
magnolia-b039a653e9e90530a76aef42df9215c151c65b67.tar.bz2
magnolia-b039a653e9e90530a76aef42df9215c151c65b67.zip
Initial checkin of messy code which appears to be a PoC
-rw-r--r--build.sbt138
-rw-r--r--core/src/main/scala/generic.scala124
-rw-r--r--examples/src/main/scala/example.scala22
-rw-r--r--project/build.properties1
-rw-r--r--project/plugins.sbt11
-rw-r--r--tests/shared/src/main/scala/magnolia/main.scala18
6 files changed, 314 insertions, 0 deletions
diff --git a/build.sbt b/build.sbt
new file mode 100644
index 0000000..79f28a2
--- /dev/null
+++ b/build.sbt
@@ -0,0 +1,138 @@
+import com.typesafe.sbt.pgp.PgpKeys.publishSigned
+import ReleaseTransformations._
+
+import sbtcrossproject.{crossProject, CrossType}
+
+lazy val core = crossProject(JSPlatform, JVMPlatform, NativePlatform)
+ .crossType(CrossType.Pure)
+ .in(file("core"))
+ .settings(buildSettings: _*)
+ .settings(publishSettings: _*)
+ .settings(scalaMacroDependencies: _*)
+ .settings(moduleName := "magnolia")
+ .nativeSettings(nativeSettings)
+
+lazy val coreJVM = core.jvm
+lazy val coreJS = core.js
+lazy val coreNative = core.native
+
+lazy val examples = crossProject(JSPlatform, JVMPlatform, NativePlatform)
+ .crossType(CrossType.Pure)
+ .in(file("examples"))
+ .settings(buildSettings: _*)
+ .settings(publishSettings: _*)
+ .settings(moduleName := "magnolia-examples")
+ .settings(quasiQuotesDependencies)
+ .nativeSettings(nativeSettings)
+ .dependsOn(core)
+
+lazy val examplesJVM = examples.jvm
+lazy val examplesJS = examples.js
+lazy val examplesNative = examples.native
+
+lazy val tests = crossProject(JSPlatform, JVMPlatform, NativePlatform)
+ .crossType(CrossType.Full)
+ .in(file("tests"))
+ .settings(buildSettings: _*)
+ .settings(noPublishSettings: _*)
+ .settings(moduleName := "magnolia-tests")
+ .settings(quasiQuotesDependencies)
+ .nativeSettings(nativeSettings)
+ .dependsOn(examples)
+
+lazy val testsJVM = tests.jvm
+lazy val testsJS = tests.js
+lazy val testsNative = tests.native
+
+lazy val buildSettings = Seq(
+ organization := "com.propensive",
+ scalaVersion := "2.12.2",
+ name := "magnolia",
+ version := "2.0.0",
+ scalacOptions ++= Seq("-deprecation", "-feature", "-Ywarn-value-discard", "-Ywarn-dead-code", "-Ywarn-nullary-unit", "-Ywarn-numeric-widen", "-Ywarn-inaccessible", "-Ywarn-adapted-args"),
+ crossScalaVersions := Seq("2.10.6", "2.11.11", "2.12.2"),
+ scmInfo := Some(ScmInfo(url("https://github.com/propensive/magnolia"),
+ "scm:git:git@github.com:propensive/magnolia.git"))
+)
+
+lazy val publishSettings = Seq(
+ homepage := Some(url("http://magnolia.propensive.com/")),
+ licenses := Seq("Apache-2.0" -> url("http://www.apache.org/licenses/LICENSE-2.0.txt")),
+ autoAPIMappings := true,
+ publishMavenStyle := true,
+ publishArtifact in Test := false,
+ pomIncludeRepository := { _ => false },
+ publishTo := {
+ val nexus = "https://oss.sonatype.org/"
+ if(isSnapshot.value)
+ Some("snapshots" at nexus + "content/repositories/snapshots")
+ else
+ Some("releases" at nexus + "service/local/staging/deploy/maven2")
+ },
+ pomExtra := (
+ <developers>
+ <developer>
+ <id>propensive</id>
+ <name>Jon Pretty</name>
+ <url>https://github.com/propensive/magnolia/</url>
+ </developer>
+ </developers>
+ ),
+ releaseProcess := Seq[ReleaseStep](
+ checkSnapshotDependencies,
+ inquireVersions,
+ runTest,
+ setReleaseVersion,
+ commitReleaseVersion,
+ tagRelease,
+ publishArtifacts,
+ setNextVersion,
+ commitNextVersion,
+ ReleaseStep(action = Command.process("sonatypeReleaseAll", _)),
+ pushChanges
+ ),
+ releaseCrossBuild := true,
+ releasePublishArtifactsAction := PgpKeys.publishSigned.value
+)
+
+lazy val noPublishSettings = Seq(
+ publish := (),
+ publishLocal := (),
+ publishArtifact := false
+)
+
+import java.io.File
+
+def crossVersionSharedSources() = Seq(
+ (unmanagedSourceDirectories in Compile) ++= { (unmanagedSourceDirectories in Compile ).value.map {
+ dir:File => new File(dir.getPath + "_" + scalaBinaryVersion.value)}}
+)
+
+lazy val nativeSettings: Seq[Setting[_]] = Seq(
+ // Scala Native not yet available for 2.12.x, so override the versions
+ scalaVersion := "2.11.11",
+ crossScalaVersions := Seq("2.10.6", "2.11.11")
+)
+
+lazy val quasiQuotesDependencies: Seq[Setting[_]] =
+ libraryDependencies ++= {
+ CrossVersion.partialVersion(scalaVersion.value) match {
+ case Some((2, scalaMajor)) if scalaMajor >= 11 => Seq()
+ case Some((2, 10)) => Seq(
+ compilerPlugin("org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full),
+ "org.scalamacros" %% "quasiquotes" % "2.1.0" cross CrossVersion.binary
+ )
+ }
+ }
+
+lazy val scalaMacroDependencies: Seq[Setting[_]] = Seq(
+ libraryDependencies += "org.typelevel" %% "macro-compat" % "1.1.1",
+ libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value,
+ libraryDependencies += "org.scala-lang" % "scala-compiler" % scalaVersion.value,
+ libraryDependencies += compilerPlugin("org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full)
+)
+
+credentials ++= (for {
+ username <- Option(System.getenv().get("SONATYPE_USERNAME"))
+ password <- Option(System.getenv().get("SONATYPE_PASSWORD"))
+} yield Credentials("Sonatype Nexus Repository Manager", "oss.sonatype.org", username, password)).toSeq
diff --git a/core/src/main/scala/generic.scala b/core/src/main/scala/generic.scala
new file mode 100644
index 0000000..3355e32
--- /dev/null
+++ b/core/src/main/scala/generic.scala
@@ -0,0 +1,124 @@
+package magnolia
+
+import scala.reflect._, macros._
+import macrocompat.bundle
+
+object GlobalState {
+ var globalState: Map[AnyRef, AnyRef] = Map()
+}
+
+@bundle
+class Macros(val c: whitebox.Context) {
+
+ def getImplicit(genericType: c.universe.Type,
+ typeConstructor: c.universe.Type/*,
+ scope: Map[c.universe.Type, c.universe.TermName]*/): c.Tree = {
+
+ import c.universe._
+
+ val scope = GlobalState.globalState.asInstanceOf[Map[Type, TermName]]
+
+ scope.get(genericType).map { nm =>
+ println("substituting "+nm)
+ q"$nm"
+ }.orElse {
+ val searchType = appliedType(typeConstructor, genericType)
+ println(s"${scope.keySet} vs $genericType")
+ if(!scope.keySet.contains(genericType)) {
+ println(s"inferring on $genericType")
+ Option({
+ val x = try c.inferImplicitValue(searchType, false, false) catch {
+ case e: Exception => null
+ }
+ println("Managed to infer "+x)
+ x
+ }).orElse {
+ println("Failed, so recursing")
+ go(genericType, typeConstructor/*, scope*/)
+ }
+ } else {
+ println("recursing")
+ go(genericType, typeConstructor/*, scope*/)
+ }
+ }.getOrElse {
+ c.abort(c.enclosingPosition, "Could not find extractor for type "+genericType)
+ }
+ }
+
+ def go(genericType: c.universe.Type,
+ typeConstructor: c.universe.Type/*,
+ scope: Map[c.universe.Type, c.universe.TermName]*/): Option[c.Tree] = {
+ import c.universe._
+
+ println(s"go($genericType, ${GlobalState.globalState})")
+
+
+ val myName = TermName(c.freshName("extractor$"))
+ println(s"before: ${GlobalState.globalState}")
+ GlobalState.globalState = GlobalState.globalState + (genericType -> myName)
+ println(s"after: ${GlobalState.globalState}")
+ val typeSymbol = genericType.typeSymbol
+ val classType = if(typeSymbol.isClass) Some(typeSymbol.asClass) else None
+ val isCaseClass = classType.map(_.isCaseClass).getOrElse(false)
+ val isSealedTrait = classType.map(_.isSealed).getOrElse(false)
+ val isAnyVal = genericType <:< typeOf[AnyVal]
+
+ val resultType = appliedType(typeConstructor, genericType)
+
+ val construct = if(isCaseClass) {
+ val implicits = genericType.decls.collect {
+ case m: MethodSymbol if m.isCaseAccessor => m.asMethod
+ }.map { p =>
+ val ret = p.returnType
+ val imp = getImplicit(ret, typeConstructor/*, newScope*/)
+ q"$imp.extract(src)"
+ }
+
+ Some(q"new $genericType(..$implicits)")
+ } else if(isSealedTrait) {
+ //println(s"$resultType a sealed trait")
+ val subtypes = classType.get.knownDirectSubclasses.to[List]
+
+ val tries = subtypes.map(_.asType.toType).map(t => getImplicit(t, typeConstructor/*, newScope*/)).foldLeft(q"null": c.Tree) { (a, b) =>
+ q"(try { $b.extract(src) } catch { case e: _root_.java.lang.Exception => $a })"
+ }
+
+ Some(q"$tries.asInstanceOf[$genericType]")
+
+ } else None
+
+ val result = construct.map { c =>
+ q"""{
+ def $myName: $resultType = new $resultType {
+ def extract(src: _root_.java.lang.String): $genericType = $c
+ }
+ $myName
+ }"""
+ }
+
+ //println(s"Generated result for $genericType: $result")
+
+ result
+ }
+ def generic[T: c.WeakTypeTag, Tc: c.WeakTypeTag]: c.Tree = try {
+ import c.universe._
+
+ val genericType: Type = weakTypeOf[T]
+ val typeConstructor: Type = weakTypeOf[Tc].typeConstructor
+
+ val result = go(genericType, typeConstructor)
+
+ println(result)
+
+ result.getOrElse {
+ c.abort(c.enclosingPosition, "Could not infer extractor. Sorry.")
+ }
+ } catch {
+ case e: Exception =>
+ println("Macro failed!!! "+e)
+ //e.printStackTrace()
+ ???
+ }
+
+}
+
diff --git a/examples/src/main/scala/example.scala b/examples/src/main/scala/example.scala
new file mode 100644
index 0000000..7e9d80b
--- /dev/null
+++ b/examples/src/main/scala/example.scala
@@ -0,0 +1,22 @@
+package magnolia
+
+import language.experimental.macros
+
+trait Extractor[T] {
+ def extract(src: String): T
+}
+
+object Extractor extends Extractor_1 {
+
+ def apply[T](fn: String => T): Extractor[T] = new Extractor[T] {
+ def extract(source: String): T = fn(source)
+ }
+
+ implicit val intExtractor: Extractor[Int] = Extractor(_.toInt)
+ implicit val stringExtractor: Extractor[String] = Extractor(identity)
+ implicit val doubleExtractor: Extractor[Double] = Extractor(_.toDouble)
+}
+
+trait Extractor_1 {
+ implicit def generic[T]: Extractor[T] = macro Macros.generic[T, Extractor[_]]
+}
diff --git a/project/build.properties b/project/build.properties
new file mode 100644
index 0000000..64317fd
--- /dev/null
+++ b/project/build.properties
@@ -0,0 +1 @@
+sbt.version=0.13.15
diff --git a/project/plugins.sbt b/project/plugins.sbt
new file mode 100644
index 0000000..dbd5625
--- /dev/null
+++ b/project/plugins.sbt
@@ -0,0 +1,11 @@
+addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.0")
+addSbtPlugin("com.github.gseitz" % "sbt-release" % "1.0.0")
+//addSbtPlugin("com.typesafe.sbt" % "sbt-git" % "0.8.4")
+addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "0.5.1")
+addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.5.0-RC2")
+addSbtPlugin("com.geirsson" % "sbt-scalafmt" % "0.4.7")
+
+addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.16")
+addSbtPlugin("org.scala-native" % "sbt-crossproject" % "0.1.0")
+addSbtPlugin("org.scala-native" % "sbt-scalajs-crossproject" % "0.1.0")
+addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.2.1")
diff --git a/tests/shared/src/main/scala/magnolia/main.scala b/tests/shared/src/main/scala/magnolia/main.scala
new file mode 100644
index 0000000..6bf5f74
--- /dev/null
+++ b/tests/shared/src/main/scala/magnolia/main.scala
@@ -0,0 +1,18 @@
+package magnolia
+
+sealed trait Bar
+case class Foo(one: Int) extends Bar
+case class Quux(two: Int, bar: Bar) extends Bar
+case class Bippy(four: Int, bar: Bar)
+case class Baz(x: Bar) extends AnyVal
+
+case class X(y: Y)
+case class Y(x: X)
+
+object Main {
+ def main(args: Array[String]): Unit = {
+ println(implicitly[Extractor[Bar]].extract("hello world"))
+
+ }
+}
+