Skip to content

Commit

Permalink
Merge pull request #2 from alexarchambault/transparent
Browse files Browse the repository at this point in the history
Use whitebox / transparent macros to have dependency literals return more refined types
  • Loading branch information
alexarchambault authored May 27, 2021
2 parents c644782 + a7cd2de commit 08d09b9
Show file tree
Hide file tree
Showing 16 changed files with 393 additions and 64 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ Exclusions can be specified in the string representations of dependencies:
```scala mdoc
val depWithExclusions = dep"io.get-coursier::coursier:2.0.6,exclude=io.argonaut%%argonaut,exclude=org.fusesource.jansi%jansi"

assert(depWithExclusions.exclude == Set(mod"io.argonaut::argonaut", mod"org.fusesource.jansi:jansi"))
assert(depWithExclusions.exclude == CovariantSet(mod"io.argonaut::argonaut", mod"org.fusesource.jansi:jansi"))
```

(Note the use of `%` as a separator in excluded dependencies).
Expand Down
59 changes: 59 additions & 0 deletions dependency/src/main/scala-2.12/dependency/CovariantSet.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package dependency

import scala.collection.immutable.SortedSet
import scala.collection.mutable.Builder
import scala.collection.IterableLike
import scala.collection.generic.CanBuildFrom
import scala.collection.AbstractIterable
import scala.collection.generic.GenericTraversableTemplate
import scala.collection.generic.GenericCompanion

class CovariantSet[+A] private (private val elements: List[A])
extends AbstractIterable[A]
with GenericTraversableTemplate[A, CovariantSet]
with IterableLike[A, CovariantSet[A]] {

override def companion: GenericCompanion[CovariantSet] = CovariantSet

def +=[B >: A](elem: B): CovariantSet[B] =
if (elements.contains(elem))
this
else
new CovariantSet[B](elem :: elements)
def iterator: Iterator[A] =
elements.iterator

// override def newBuilder[A]: Builder[A, CovariantSet[A]] = ???

override def equals(obj: Any): Boolean =
if (obj == null) false
else if (this eq obj.asInstanceOf[AnyRef]) true
else if (obj.isInstanceOf[CovariantSet[_]]) {
val other = obj.asInstanceOf[CovariantSet[_]]
elements.toSet == other.elements.toSet
}
else super.equals(obj)

}

object CovariantSet extends GenericCompanion[CovariantSet] {

implicit def cbf[A, B]: CanBuildFrom[CovariantSet[A], B, CovariantSet[B]] =
new CanBuildFrom[CovariantSet[A], B, CovariantSet[B]] {
def apply(): scala.collection.mutable.Builder[B,dependency.CovariantSet[B]] =
newBuilder[B]
def apply(from: dependency.CovariantSet[A]): scala.collection.mutable.Builder[B,dependency.CovariantSet[B]] =
newBuilder[B]
}

def newBuilder[A]: Builder[A, CovariantSet[A]] =
new Builder[A, CovariantSet[A]] {
val underlying = collection.mutable.Set.newBuilder[A]
def +=(elem: A): this.type = {
underlying += elem
this
}
def clear(): Unit = underlying.clear()
def result(): CovariantSet[A] = new CovariantSet[A](underlying.result().toList)
}
}
50 changes: 50 additions & 0 deletions dependency/src/main/scala-2.13/dependency/CovariantSet.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package dependency

import scala.collection.IterableOps
import scala.collection.IterableFactoryDefaults
import scala.collection.IterableFactory
import scala.collection.mutable.Builder

class CovariantSet[+A] private (private val elements: List[A])
extends Iterable[A]
with IterableOps[A, CovariantSet, CovariantSet[A]]
with IterableFactoryDefaults[A, CovariantSet] {

def +=[B >: A](elem: B): CovariantSet[B] =
if (elements.contains(elem))
this
else
new CovariantSet[B](elem :: elements)
def iterator: Iterator[A] =
elements.iterator

override def iterableFactory: IterableFactory[CovariantSet] =
CovariantSet

override def equals(obj: Any): Boolean =
if (obj == null) false
else if (this eq obj.asInstanceOf[AnyRef]) true
else if (obj.isInstanceOf[CovariantSet[_]]) {
val other = obj.asInstanceOf[CovariantSet[_]]
elements.toSet == other.elements.toSet
}
else super.equals(obj)

}

object CovariantSet extends IterableFactory[CovariantSet] {
def from[A](source: IterableOnce[A]): CovariantSet[A] =
new CovariantSet[A](source.toList.distinct)
def empty[A]: CovariantSet[A] =
new CovariantSet[A](Nil)
def newBuilder[A]: Builder[A, CovariantSet[A]] =
new Builder[A, CovariantSet[A]] {
val underlying = collection.mutable.Set.newBuilder[A]
def addOne(elem: A): this.type = {
underlying.addOne(elem)
this
}
def clear(): Unit = underlying.clear()
def result(): CovariantSet[A] = new CovariantSet[A](underlying.result().toList)
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package dependency
package literal

import scala.reflect.macros.blackbox
import scala.reflect.macros.whitebox
import dependency.parser.DependencyParser

class DependencyLiteralMacros(override val c: blackbox.Context) extends ModuleLiteralMacros(c) {
class DependencyLiteralMacros(override val c: whitebox.Context) extends ModuleLiteralMacros(c) {
import c.universe._

private def optionString(opt: Option[String], mappings: Mappings): c.Expr[Option[String]] = {
Expand All @@ -24,21 +24,25 @@ class DependencyLiteralMacros(override val c: blackbox.Context) extends ModuleLi
c.Expr(q"_root_.scala.collection.immutable.Map[_root_.java.lang.String, _root_.scala.Option[_root_.java.lang.String]](..$entries)")
}

private def dependencyExpr(dep: AnyDependency, mappings: Mappings): c.Expr[AnyDependency] = {
private def dependencyExpr(dep: AnyDependency, mappings: Mappings): c.Tree = {
val (nameAttr, modExpr) = moduleExpr(dep.module, mappings)
val params = stringOptionStringMap(dep.userParams, mappings)
val exclude = dep.exclude.map(moduleExpr(_, mappings)._2)
c.Expr(q"""
_root_.dependency.DependencyLike[$nameAttr, _root_.dependency.NameAttributes](
val excludeTpe =
if (dep.exclude.forall(_.nameAttributes == NoAttributes)) tq"_root_.dependency.NoAttributes.type"
else if (dep.exclude.forall(_.nameAttributes.isInstanceOf[ScalaNameAttributes])) tq"_root_.dependency.ScalaNameAttributes"
else tq"_root_.dependency.NameAttributes"
q"""
_root_.dependency.DependencyLike[$nameAttr, $excludeTpe](
$modExpr,
${applyMappings(dep.version, mappings)},
_root_.scala.collection.immutable.Set[_root_.dependency.AnyModule](..$exclude),
_root_.dependency.CovariantSet[_root_.dependency.ModuleLike[$excludeTpe]](..${exclude.toSeq}),
$params
)
""")
"""
}

def dependency(args: c.Expr[Any]*): c.Expr[AnyDependency] = {
def dependency(args: c.Tree*): c.Tree = {
val inputs = unsafeGetPrefixStrings()
val mappings0 = mappings(args)
val input0 = input(inputs, mappings0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ package dependency.literal
import java.util.UUID

import scala.language.experimental.macros
import scala.reflect.macros.blackbox
import scala.reflect.macros.whitebox

abstract class LiteralMacros(val c: blackbox.Context) {
abstract class LiteralMacros(val c: whitebox.Context) {
import c.universe._

protected def unsafeGetPrefixString(): String =
Expand Down Expand Up @@ -39,7 +39,7 @@ abstract class LiteralMacros(val c: blackbox.Context) {
helper(0)
}

private def insertExpr(str: String, idLen: Int, insert: c.Expr[Any], indices: List[Int]): c.Tree =
private def insertExpr(str: String, idLen: Int, insert: c.Tree, indices: List[Int]): c.Tree =
indices match {
case Nil => q"$str"
case idx :: tail =>
Expand All @@ -48,7 +48,7 @@ abstract class LiteralMacros(val c: blackbox.Context) {
q"$prefixExpr + $insert + ${suffix.substring(idLen)}"
}

protected final type Mappings = Seq[(String, c.Expr[Any])]
protected final type Mappings = Seq[(String, c.Tree)]

protected def applyMappings(str: String, mappings: Mappings): c.Expr[String] = {
val matchOpt = mappings
Expand All @@ -69,7 +69,7 @@ abstract class LiteralMacros(val c: blackbox.Context) {
}
}

def mappings(args: Seq[c.Expr[Any]]): Mappings =
def mappings(args: Seq[c.Tree]): Mappings =
args.map(arg => (UUID.randomUUID().toString.filter(_ != '-'), arg))
def input(inputs: Seq[String], mappings: Mappings): String =
(inputs.zip(mappings).flatMap { case (s, (id, _)) => Seq(s, id) } ++ inputs.drop(mappings.length)).mkString
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,22 @@ package literal

import dependency.parser.ModuleParser

import scala.reflect.macros.blackbox
import scala.reflect.macros.whitebox

// inspired from https://github.com/coursier/interface/blob/0bac6a4c93dfafeb79bd924a2f4bcb690ba4510a/interpolators/src/main/scala/coursierapi/Interpolators.scala

class ModuleLiteralMacros(override val c: blackbox.Context) extends LiteralMacros(c) {
class ModuleLiteralMacros(override val c: whitebox.Context) extends LiteralMacros(c) {

import c.universe._

private def booleanOptExpr(value: Option[Boolean]): c.Expr[Option[Boolean]] = {
val expr = value match {
private def booleanOptExpr(value: Option[Boolean]): c.Tree =
value match {
case None => q"_root_.scala.None"
case Some(false) => q"_root_.scala.Some(false)"
case Some(true) => q"_root_.scala.Some(true)"
}
c.Expr(expr)
}

protected def nameAttributesExpr(nameAttributes: NameAttributes): (Tree, c.Expr[NameAttributes]) = {
protected def nameAttributesExpr(nameAttributes: NameAttributes): (Tree, Tree) = {
val (tpe, expr) = nameAttributes match {
case NoAttributes =>
(tq"_root_.dependency.NoAttributes.type", q"_root_.dependency.NoAttributes")
Expand All @@ -29,29 +27,29 @@ class ModuleLiteralMacros(override val c: blackbox.Context) extends LiteralMacro
val expr0 = q"_root_.dependency.ScalaNameAttributes(${booleanOptExpr(fullCrossVersion)}, ${booleanOptExpr(platform)})"
(tpe0, expr0)
}
(tpe, c.Expr(expr))
(tpe, expr)
}

private def stringStringMap(map: Map[String, String], mappings: Mappings): c.Expr[Map[String, String]] = {
val entries = map.toVector.sorted.map { case (k, v) => c.Expr(q"_root_.scala.Tuple2(${applyMappings(k, mappings)}, ${applyMappings(v, mappings)})") }
c.Expr(q"_root_.scala.collection.immutable.Map[_root_.java.lang.String, _root_.java.lang.String](..$entries)")
private def stringStringMap(map: Map[String, String], mappings: Mappings): c.Tree = {
val entries = map.toVector.sorted.map { case (k, v) => q"_root_.scala.Tuple2(${applyMappings(k, mappings)}, ${applyMappings(v, mappings)})" }
q"_root_.scala.collection.immutable.Map[_root_.java.lang.String, _root_.java.lang.String](..$entries)"
}

protected def moduleExpr(mod: AnyModule, mappings: Mappings): (Tree, c.Expr[AnyModule]) = {
protected def moduleExpr(mod: AnyModule, mappings: Mappings): (Tree, c.Tree) = {
val (nameAttrTpe, nameAttr) = nameAttributesExpr(mod.nameAttributes)
val attr = stringStringMap(mod.attributes, mappings)
val expr = c.Expr(q"""
val expr = q"""
_root_.dependency.ModuleLike[$nameAttrTpe](
${applyMappings(mod.organization, mappings)},
${applyMappings(mod.name, mappings)},
$nameAttr,
$attr
)
""")
"""
(nameAttrTpe, expr)
}

def module(args: c.Expr[Any]*): c.Expr[AnyModule] = {
def module(args: c.Tree*): c.Tree = {
val inputs = unsafeGetPrefixStrings()
val mappings0 = mappings(args)
val input0 = input(inputs, mappings0)
Expand Down
50 changes: 50 additions & 0 deletions dependency/src/main/scala-3/dependency/CovariantSet.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package dependency

import scala.collection.IterableOps
import scala.collection.IterableFactoryDefaults
import scala.collection.IterableFactory
import scala.collection.mutable.Builder

class CovariantSet[+A] private (private val elements: List[A])
extends Iterable[A]
with IterableOps[A, CovariantSet, CovariantSet[A]]
with IterableFactoryDefaults[A, CovariantSet] {

def +=[B >: A](elem: B): CovariantSet[B] =
if (elements.contains(elem))
this
else
new CovariantSet[B](elem :: elements)
def iterator: Iterator[A] =
elements.iterator

override def iterableFactory: IterableFactory[CovariantSet] =
CovariantSet

override def equals(obj: Any): Boolean =
if (obj == null) false
else if (this eq obj.asInstanceOf[AnyRef]) true
else if (obj.isInstanceOf[CovariantSet[_]]) {
val other = obj.asInstanceOf[CovariantSet[_]]
elements.toSet == other.elements.toSet
}
else super.equals(obj)

}

object CovariantSet extends IterableFactory[CovariantSet] {
def from[A](source: IterableOnce[A]): CovariantSet[A] =
new CovariantSet[A](source.toList.distinct)
def empty[A]: CovariantSet[A] =
new CovariantSet[A](Nil)
def newBuilder[A]: Builder[A, CovariantSet[A]] =
new Builder[A, CovariantSet[A]] {
val underlying = collection.mutable.Set.newBuilder[A]
def addOne(elem: A): this.type = {
underlying.addOne(elem)
this
}
def clear(): Unit = underlying.clear()
def result(): CovariantSet[A] = new CovariantSet[A](underlying.result().toList)
}
}
Loading

0 comments on commit 08d09b9

Please sign in to comment.