From adbd336ce6d6ead9ac299c225cbbffdcd74c883c Mon Sep 17 00:00:00 2001 From: Kai Date: Fri, 7 May 2021 00:39:16 +0100 Subject: [PATCH 1/5] Add option to use underscore symbol `_` instead of `*` to define anonymous type lambdas The syntax roughly follows the [proposed new syntax for wildcards and placeholders](https://dotty.epfl.ch/docs/reference/changed-features/wildcards.html#migration-strategy) for Scala 3.2+ and is designed to allow cross-compilation of libraries between Scala 2 and Scala 3 while using the new Scala 3 syntax for both versions. To enable this mode, add `-P:kind-projector:underscore-placeholders` to your scalac command-line. In sbt you may do this as follows: ```scala ThisBuild / scalacOptions += "-P:kind-projector:underscore-placeholders" ``` This mode is designed to be used with scalac versions `2.12.14`+ and `2.13.6`+, these versions add an the ability to use `?` as the existential type wildcard ([scala/scala#9560](https://github.com/scala/scala/pull/9560)), allowing to repurpose the underscore without losing the ability to write existential types. It is not advised that you use this mode with older versions of scalac or without `-Xsource:3` flag, since you will lose the underscore syntax entirely. Here are a few examples: ```scala Tuple2[_, Double] // equivalent to: type R[A] = Tuple2[A, Double] Either[Int, +_] // equivalent to: type R[+A] = Either[Int, A] Function2[-_, Long, +_] // equivalent to: type R[-A, +B] = Function2[A, Long, B] EitherT[_[_], Int, _] // equivalent to: type R[F[_], B] = EitherT[F, Int, B] ``` Examples with `-Xsource:3`'s `?`-wildcard: ```scala Tuple2[_, ?] // equivalent to: type R[A] = Tuple2[A, x] forSome { type x } Either[?, +_] // equivalent to: type R[+A] = Either[x, A] forSome { type x } Function2[-_, ?, +_] // equivalent to: type R[-A, +B] = Function2[A, x, B] forSome { type x } EitherT[_[_], ?, _] // equivalent to: type R[F[_], B] = EitherT[F, x, B] forSome { type x } ``` --- README.md | 31 +++++++ build.sbt | 12 ++- .../scala-newPlugin/PluginOptionsCompat.scala | 10 +++ .../scala-oldPlugin/PluginOptionsCompat.scala | 16 ++++ src/main/scala/KindProjector.scala | 59 ++++++++++++- src/test/scala/issue80.scala | 1 + src/test/scala/underscores/functor.scala | 13 +++ src/test/scala/underscores/issue80.scala | 23 +++++ src/test/scala/underscores/nested.scala | 13 +++ src/test/scala/underscores/polylambda.scala | 67 +++++++++++++++ src/test/scala/underscores/test.scala | 86 +++++++++++++++++++ src/test/scala/underscores/tony.scala | 43 ++++++++++ 12 files changed, 367 insertions(+), 7 deletions(-) create mode 100644 src/main/scala-newPlugin/PluginOptionsCompat.scala create mode 100644 src/main/scala-oldPlugin/PluginOptionsCompat.scala create mode 100644 src/test/scala/underscores/functor.scala create mode 100644 src/test/scala/underscores/issue80.scala create mode 100644 src/test/scala/underscores/nested.scala create mode 100644 src/test/scala/underscores/polylambda.scala create mode 100644 src/test/scala/underscores/test.scala create mode 100644 src/test/scala/underscores/tony.scala diff --git a/README.md b/README.md index 781a0be..e0d640f 100644 --- a/README.md +++ b/README.md @@ -131,6 +131,37 @@ lambda is only used in the body once, and in the same order. For more complex type lambda expressions, you will need to use the function syntax. +#### Inline Underscore Syntax + +Since version `0.13.0` kind-projector adds an option to use underscore symbol `_` instead of `*` to define anonymous type lambdas. +The syntax roughly follows the [proposed new syntax for wildcards and placeholders](https://dotty.epfl.ch/docs/reference/changed-features/wildcards.html#migration-strategy) for Scala 3.2+ and is designed to allow cross-compilation of libraries between Scala 2 and Scala 3 while using the new Scala 3 syntax for both versions. + +To enable this mode, add `-P:kind-projector:underscore-placeholders` to your scalac command-line. In sbt you may do this as follows: + +```scala +ThisBuild / scalacOptions += "-P:kind-projector:underscore-placeholders" +``` + +This mode is designed to be used with scalac versions `2.12.14`+ and `2.13.6`+, these versions add an the ability to use `?` as the existential type wildcard ([scala/scala#9560](https://github.com/scala/scala/pull/9560)), allowing to repurpose the underscore without losing the ability to write existential types. It is not advised that you use this mode with older versions of scalac or without `-Xsource:3` flag, since you will lose the underscore syntax entirely. + +Here are a few examples: + +```scala +Tuple2[_, Double] // equivalent to: type R[A] = Tuple2[A, Double] +Either[Int, +_] // equivalent to: type R[+A] = Either[Int, A] +Function2[-_, Long, +_] // equivalent to: type R[-A, +B] = Function2[A, Long, B] +EitherT[_[_], Int, _] // equivalent to: type R[F[_], B] = EitherT[F, Int, B] +``` + +Examples with `-Xsource:3`'s `?`-wildcard: + +```scala +Tuple2[_, ?] // equivalent to: type R[A] = Tuple2[A, x] forSome { type x } +Either[?, +_] // equivalent to: type R[+A] = Either[x, A] forSome { type x } +Function2[-_, ?, +_] // equivalent to: type R[-A, +B] = Function2[A, x, B] forSome { type x } +EitherT[_[_], ?, _] // equivalent to: type R[F[_], B] = EitherT[F, x, B] forSome { type x } +``` + ### Function Syntax The more powerful syntax to use is the function syntax. This syntax diff --git a/build.sbt b/build.sbt index 3b0e66f..571b33b 100644 --- a/build.sbt +++ b/build.sbt @@ -18,7 +18,7 @@ inThisBuild { "2.13.2", "2.13.3", "2.13.4", - "2.13.5" + "2.13.5", ), organization := "org.typelevel", licenses += ("MIT", url("http://opensource.org/licenses/MIT")), @@ -63,7 +63,7 @@ inThisBuild { val HasScalaVersion = { object Matcher { - def unapply(versionString: String) = + def unapply(versionString: String) = versionString.takeWhile(ch => ch != '-').split('.').toList.map(str => scala.util.Try(str.toInt).toOption) match { case List(Some(epoch), Some(major), Some(minor)) => Some((epoch, major, minor)) case _ => None @@ -84,6 +84,11 @@ def hasNewParser(versionString: String) = versionString match { case _ => false } +def hasNewPlugin(versionString: String) = versionString match { + case HasScalaVersion(2, 10, _) => false + case _ => true +} + lazy val `kind-projector` = project .in(file(".")) .settings( @@ -100,6 +105,7 @@ lazy val `kind-projector` = project val suffices = (if (hasNewParser(sv)) "-newParser" else "-oldParser") :: (if (hasNewReporting(sv)) "-newReporting" else "-oldReporting") :: + (if (hasNewPlugin(sv)) "-newPlugin" else "-oldPlugin") :: Nil suffices.map(suffix => file(dir.getPath + suffix)) } @@ -127,7 +133,7 @@ lazy val `kind-projector` = project Test / scalacOptions ++= (scalaVersion.value match { case HasScalaVersion(2, 13, n) if n >= 2 => List("-Wconf:src=WarningSuppression.scala:error") case _ => Nil - }), + }) ++ List("-P:kind-projector:underscore-placeholders"), console / initialCommands := "import d_m._", Compile / console / scalacOptions := Seq("-language:_", "-Xplugin:" + (Compile / packageBin).value), Test / console / scalacOptions := (Compile / console / scalacOptions).value, diff --git a/src/main/scala-newPlugin/PluginOptionsCompat.scala b/src/main/scala-newPlugin/PluginOptionsCompat.scala new file mode 100644 index 0000000..3fa91b3 --- /dev/null +++ b/src/main/scala-newPlugin/PluginOptionsCompat.scala @@ -0,0 +1,10 @@ +package d_m + +import scala.tools.nsc.plugins.Plugin + +trait PluginOptionsCompat { + def pluginOptions(plugin: Plugin) = plugin.options +} + +//compatibility stub +trait PluginCompat diff --git a/src/main/scala-oldPlugin/PluginOptionsCompat.scala b/src/main/scala-oldPlugin/PluginOptionsCompat.scala new file mode 100644 index 0000000..f07a70a --- /dev/null +++ b/src/main/scala-oldPlugin/PluginOptionsCompat.scala @@ -0,0 +1,16 @@ +package d_m + +import scala.tools.nsc.plugins.Plugin + +trait PluginOptionsCompat { + def pluginOptions(plugin: Plugin) = plugin.asInstanceOf[PluginCompat].options +} + +trait PluginCompat extends Plugin { + var options: List[String] = _ + override def processOptions(options: List[String], error: String => Unit): Unit = { + this.options = options + init(options, error) + } + def init(options: List[String], error: String => Unit): Boolean +} diff --git a/src/main/scala/KindProjector.scala b/src/main/scala/KindProjector.scala index b0ea954..83c749d 100644 --- a/src/main/scala/KindProjector.scala +++ b/src/main/scala/KindProjector.scala @@ -12,14 +12,28 @@ import nsc.ast.TreeDSL import scala.reflect.NameTransformer import scala.collection.mutable -class KindProjector(val global: Global) extends Plugin { +class KindProjector(val global: Global) extends Plugin with PluginCompat { val name = "kind-projector" val description = "Expand type lambda syntax" + + override val optionsHelp = Some(Seq( + "-P:kind-projector:underscore-placeholders - treat underscore as a type lambda placeholder,", + "disables Scala 2 wildcards, you must separately enable `-Xsource:3` option to be able to", + "write wildcards using `?` symbol").mkString(" ")) + + + override def init(options: List[String], error: String => Unit): Boolean = { + if (options.exists(_ != "underscore-placeholders")) { + error(s"Error: $name takes no options except `-P:kind-projector:underscore-placeholders`, but got ${options.mkString(",")}") + } + true + } + val components = new KindRewriter(this, global) :: Nil } class KindRewriter(plugin: Plugin, val global: Global) - extends PluginComponent with Transform with TypingTransformers with TreeDSL with ReportingCompat { + extends PluginComponent with Transform with TypingTransformers with TreeDSL with PluginOptionsCompat with ReportingCompat { import global._ @@ -32,6 +46,10 @@ class KindRewriter(plugin: Plugin, val global: Global) lazy val useAsciiNames: Boolean = System.getProperty("kp:genAsciiNames") == "true" + lazy val useUnderscoresForTypeLambda: Boolean = { + pluginOptions(plugin).contains("underscore-placeholders") + } + def newTransformer(unit: CompilationUnit): MyTransformer = new MyTransformer(unit) @@ -54,15 +72,28 @@ class KindRewriter(plugin: Plugin, val global: Global) val InvPlaceholder = newTypeName("$times") val CoPlaceholder = newTypeName("$plus$times") val ContraPlaceholder = newTypeName("$minus$times") - + val TermLambda1 = TypeLambda1.toTermName val TermLambda2 = TypeLambda2.toTermName + object InvPlaceholderScala3 { + def apply(n: Name): Boolean = n match { case InvPlaceholderScala3() => true; case _ => false } + def unapply(t: TypeName): Boolean = t.startsWith("_$") && t.drop(2).decoded.forall(_.isDigit) + } + val CoPlaceholderScala3 = newTypeName("$plus_") + val ContraPlaceholderScala3 = newTypeName("$minus_") + object Placeholder { def unapply(name: TypeName): Option[Variance] = name match { case InvPlaceholder => Some(Invariant) case CoPlaceholder => Some(Covariant) case ContraPlaceholder => Some(Contravariant) + case _ if useUnderscoresForTypeLambda => name match { + case InvPlaceholderScala3() => Some(Invariant) + case CoPlaceholderScala3 => Some(Covariant) + case ContraPlaceholderScala3 => Some(Contravariant) + case _ => None + } case _ => None } } @@ -248,9 +279,20 @@ class KindRewriter(plugin: Plugin, val global: Global) case (ExistentialTypeTree(AppliedTypeTree(Ident(Placeholder(variance)), ps), _), i) => (Ident(newParamName(i)), Some(Left((variance, ps.map(makeComplexTypeParam))))) case (a, i) => - (super.transform(a), None) + // Using super.transform in existential type case in underscore mode + // skips the outer `ExistentialTypeTree` (reproduces in nested.scala test) + // and produces invalid trees where the unused underscore variables are not cleaned up + // by the current transformer + // I do not know why! Using `this.transform` instead works around the issue, + // however it seems to have worked correctly all this time non-underscore mode, so + // we keep calling super.transform to not change anything for existing code in classic mode. + val transformedA = + if (useUnderscoresForTypeLambda) this.transform(a) + else super.transform(a) + (transformedA, None) } + // for each placeholder, create a type parameter val innerTypes = xyz.collect { case (Ident(name), Some(Right(variance))) => @@ -331,6 +373,15 @@ class KindRewriter(plugin: Plugin, val global: Global) case AppliedTypeTree(Ident(TypeLambda2), AppliedTypeTree(target, a :: as) :: Nil) => validateLambda(tree.pos, target, a, as) + // Either[_, Int] case (if `underscore-placeholders` is enabled) + case ExistentialTypeTree(AppliedTypeTree(t, as), params) if useUnderscoresForTypeLambda => + val nonUnderscoreExistentials = params.filterNot { + case p: MemberDef => InvPlaceholderScala3(p.name) + case _ => true + } + val nt = atPos(tree.pos.makeTransparent)(handlePlaceholders(t, as)) + if (nonUnderscoreExistentials.isEmpty) nt else ExistentialTypeTree(nt, nonUnderscoreExistentials) + // Either[?, Int] case (if no ? present this is a noop) case AppliedTypeTree(t, as) => atPos(tree.pos.makeTransparent)(handlePlaceholders(t, as)) diff --git a/src/test/scala/issue80.scala b/src/test/scala/issue80.scala index 2407f19..a446718 100644 --- a/src/test/scala/issue80.scala +++ b/src/test/scala/issue80.scala @@ -17,4 +17,5 @@ object Coproduct { case Right(bx) => Coproduct(Right(g(bx))) } } + def test[X[_]]: Bifunctor[({ type L[F[_[_]], G[_[_]]] = Coproduct[F, G, X] })#L] = coproductBifunctor[X] } diff --git a/src/test/scala/underscores/functor.scala b/src/test/scala/underscores/functor.scala new file mode 100644 index 0000000..1567c1b --- /dev/null +++ b/src/test/scala/underscores/functor.scala @@ -0,0 +1,13 @@ +package underscores + +trait Functor[M[_]] { + def fmap[A, B](fa: M[A])(f: A => B): M[B] +} + +class EitherRightFunctor[L] extends Functor[Either[L, _]] { + def fmap[A, B](fa: Either[L, A])(f: A => B): Either[L, B] = + fa match { + case Right(a) => Right(f(a)) + case Left(l) => Left(l) + } +} diff --git a/src/test/scala/underscores/issue80.scala b/src/test/scala/underscores/issue80.scala new file mode 100644 index 0000000..1fcdbfb --- /dev/null +++ b/src/test/scala/underscores/issue80.scala @@ -0,0 +1,23 @@ +package underscores + +trait ~~>[A[_[_]], B[_[_]]] { + def apply[X[_]](a: A[X]): B[X] +} + +trait Bifunctor[F[_[_[_]], _[_[_]]]] { + def bimap[A[_[_]], B[_[_]], C[_[_]], D[_[_]]](fab: F[A, B])(f: A ~~> C, g: B ~~> D): F[C, D] +} + +final case class Coproduct[A[_[_]], B[_[_]], X[_]](run: Either[A[X], B[X]]) + +object Coproduct { + def coproductBifunctor[X[_]]: Bifunctor[Coproduct[_[_[_]], _[_[_]], X]] = + new Bifunctor[Coproduct[_[_[_]], _[_[_]], X]] { + def bimap[A[_[_]], B[_[_]], C[_[_]], D[_[_]]](abx: Coproduct[A, B, X])(f: A ~~> C, g: B ~~> D): Coproduct[C, D, X] = + abx.run match { + case Left(ax) => Coproduct(Left(f(ax))) + case Right(bx) => Coproduct(Right(g(bx))) + } + } + def test[X[_]]: Bifunctor[({ type L[F[_[_]], G[_[_]]] = Coproduct[F, G, X] })#L] = coproductBifunctor[X] +} diff --git a/src/test/scala/underscores/nested.scala b/src/test/scala/underscores/nested.scala new file mode 100644 index 0000000..ffe1166 --- /dev/null +++ b/src/test/scala/underscores/nested.scala @@ -0,0 +1,13 @@ +package underscores + +// // From https://github.com/non/kind-projector/issues/20 +// import scala.language.higherKinds + +object KindProjectorWarnings { + trait Foo[F[_], A] + trait Bar[A, B] + + def f[G[_]]: Unit = () + + f[Foo[Bar[Int, _], _]] // shadowing warning +} diff --git a/src/test/scala/underscores/polylambda.scala b/src/test/scala/underscores/polylambda.scala new file mode 100644 index 0000000..936d179 --- /dev/null +++ b/src/test/scala/underscores/polylambda.scala @@ -0,0 +1,67 @@ +package underscores + +trait ~>[-F[_], +G[_]] { + def apply[A](x: F[A]): G[A] +} +trait ~>>[-F[_], +G[_]] { + def dingo[B](x: F[B]): G[B] +} +final case class Const[A, B](getConst: A) + +class PolyLambdas { + type ToSelf[F[_]] = F ~> F + + val kf1 = Lambda[Option ~> Vector](_.iterator.toVector) + + val kf2 = λ[Vector ~> Option] { + case Vector(x) => Some(x) + case _ => None + } + + val kf3 = λ[ToSelf[Vector]](_.reverse) + + val kf4 = λ[Option ~>> Option].dingo(_ flatMap (_ => None)) + + val kf5 = λ[Map[_, Int] ~> Map[_, Long]](_.map { case (k, v) => (k, v.toLong) }.toMap) + + val kf6 = λ[ToSelf[Map[_, Int]]](_.map { case (k, v) => (k, v * 2) }.toMap) + + implicit class FGOps[F[_], A](x: F[A]) { + def ntMap[G[_]](kf: F ~> G): G[A] = kf(x) + } + + // Scala won't infer the unary type constructor alias from a + // tuple. I'm not sure how it even could, so we'll let it slide. + type PairWithInt[A] = (A, Int) + def mkPair[A](x: A, y: Int): PairWithInt[A] = x -> y + val pairMap = λ[ToSelf[PairWithInt]] { case (k, v) => (k, v * 2) } + val tupleTakeFirst = λ[λ[A => (A, Int)] ~> List](x => List(x._1)) + + // All these formulations should be equivalent. + def const1[A] = λ[ToSelf[Const[A, _]]](x => x) + def const2[A] : ToSelf[Const[A, _]] = λ[Const[A, _] ~> Const[A, _]](x => x) + def const3[A] : Const[A, _] ~> Const[A, _] = λ[ToSelf[Const[A, _]]](x => x) + def const4[A] = λ[Const[A, _] ~> Const[A, _]](x => x) + def const5[A] : ToSelf[Const[A, _]] = λ[ToSelf[λ[B => Const[A, B]]]](x => x) + def const6[A] : Const[A, _] ~> Const[A, _] = λ[ToSelf[λ[B => Const[A, B]]]](x => x) + + @org.junit.Test + def polylambda(): Unit = { + assert(kf1(None) == Vector()) + assert(kf1(Some("a")) == Vector("a")) + assert(kf1(Some(5d)) == Vector(5d)) + assert(kf2(Vector(5)) == Some(5)) + assert(kf3(Vector(1, 2)) == Vector(2, 1)) + assert(kf4.dingo(Some(5)) == None) + assert(kf5(Map("a" -> 5)) == Map("a" -> 5)) + assert(kf6(Map("a" -> 5)) == Map("a" -> 10)) + + assert((mkPair("a", 1) ntMap pairMap) == ("a" -> 2)) + assert((mkPair(Some(true), 1) ntMap pairMap) == (Some(true) -> 2)) + + assert(mkPair('a', 1).ntMap(tupleTakeFirst) == List('a')) + // flatten works, whereas it would be a static error in the + // line above. That's pretty poly! + assert(mkPair(Some(true), 1).ntMap(tupleTakeFirst).flatten == List(true)) + } +} diff --git a/src/test/scala/underscores/test.scala b/src/test/scala/underscores/test.scala new file mode 100644 index 0000000..9049b2c --- /dev/null +++ b/src/test/scala/underscores/test.scala @@ -0,0 +1,86 @@ +package underscores + +object Test { + // some type-level helper methods + def foo[T] = () + def bar[T[_]] = () + def baz[T[_, _]] = () + + // used for seeing what kind of tree we will start with + type ?? = Unit + foo[Either[Int, ??]] + foo[Tuple3[Int, ??, ??]] + + // used for seeing what kind of tree we want to end up with + bar[({type L[X] = Either[Int, X]})#L] + baz[({type L[X,Y] = Tuple3[Int, X, Y]})#L] + + // used to test the plugin + bar[Either[Int, _]] + baz[Tuple3[Int, _, _]] + baz[Tuple3[_, Int, _]] + + // should not be changed by the plugin + foo[Either[Int, Double]] + foo[Tuple3[Int, Int, Double]] + + // xyz + type Fake[A] = A + foo[Fake[(Int, Double) => Either[Double, Int]]] + baz[Lambda[(A, B) => Either[B, A]]] + + class Graph { type Node } + foo[Graph { type Node = Int }] + bar[Lambda[N => Graph { type Node = N }]] + //bar[Graph { type Node = ? }] // TODO, maybe? + //bar[Graph#?Node] // TODO, maybe? + + // higher order + def qux[T[_[_]]] = () + qux[({type L[A[_]] = Unit})#L] + qux[Lambda[A[_] => Unit]] + qux[Lambda[A[B] => Unit]] + + trait Functor[F[_]] + trait EitherT[F[_], A, B] + qux[Functor[_[_]]] + qux[EitherT[_[_], Int, Double]] + + // higher higher order + def vex[T[_[_[_]]]] = () + vex[({type L[A[_[_]]] = Unit})#L] + vex[Lambda[A[_[_]] => Unit]] + vex[Lambda[A[B[_]] => Unit]] + vex[Lambda[A[_[C]] => Unit]] + vex[Lambda[A[B[C]] => Unit]] + + trait FunctorK[F[_[_]]] + vex[FunctorK[_[_[_]]]] + + def hex[T[_[_[_[_]]]]] = () + hex[({type L[A[_[_[_]]]] = Unit})#L] + hex[Lambda[A[_[_[_]]] => Unit]] + + // covariant + def mux[T[+_]] = () + mux[({type L[+A] = Either[A, Int]})#L] + mux[Either[`+_`, Int]] + mux[Lambda[`+A` => Either[A, Int]]] + mux[Lambda[+[A] => Either[A, Int]]] + + // contravariant + def bux[T[-_, +_]] = () + bux[({type L[-A, +B] = Function2[A, Int, B]})#L] + bux[Function2[`-_`, Int, `+_`]] + bux[Lambda[(`-A`, `+B`) => Function2[A, Int, B]]] + bux[Lambda[(-[A], +[B]) => Function2[A, Int, B]]] + + // higher-kinded variance + trait ~>[-F[_], +G[_]] + def tux[T[-F[_]]] = () + def hux[T[+G[_]]] = () + tux[~>[`-_`[_], Option]] + hux[~>[Option, `+_`[_]]] + tux[Lambda[`-F[_]` => ~>[F, Option]]] + hux[Lambda[`+G[_]` => ~>[Option, G]]] +} diff --git a/src/test/scala/underscores/tony.scala b/src/test/scala/underscores/tony.scala new file mode 100644 index 0000000..61781c1 --- /dev/null +++ b/src/test/scala/underscores/tony.scala @@ -0,0 +1,43 @@ +package underscores.tony + +// code by tony morris (slightly modified) +// sent to scala-users recently + +trait Functor[F[_]] { + def fmap[A, B](f: A => B): F[A] => F[B] +} + +trait FlatMap[F[_]] extends Functor[F] { + def flatMap[A, B](f: A => F[B]): F[A] => F[B] +} + +// implicitly is so ugly +object FlatMap { + def apply[A[_]](implicit ev:FlatMap[A]) = ev +} + +trait Semigroupoid[~>[_, _]] { + def compose[A, B, C]: (A ~> B) => (B ~> C) => (A ~> C) +} + +case class Kleisli[A, F[_], B](k: A => F[B]) + +object Main { + // this was what tony wrote + def KleisliSemigroupoid[F[_]: FlatMap]: Semigroupoid[({ type lam[a, b]=Kleisli[a, F, b] })#lam] = { + new Semigroupoid[({ type lam[a, b]=Kleisli[a, F, b] })#lam] { + def compose[A, B, C] = { + f => g => Kleisli(a => implicitly[FlatMap[F]].flatMap(g.k)(f k a)) + } + } + } + + // using the plugin, this should be the same (but hopefully easier to read) + def KleisliSemigroupoid2[F[_]: FlatMap]: Semigroupoid[Kleisli[_, F, _]] = { + new Semigroupoid[Kleisli[_, F, _]] { + def compose[A, B, C] = { + f => g => Kleisli(a => FlatMap[F].flatMap(g.k)(f.k(a))) + } + } + } +} From 02ab0b59a885710ec0b936843eae8108a79ae4f6 Mon Sep 17 00:00:00 2001 From: Kai <450507+neko-kai@users.noreply.github.com> Date: Mon, 10 May 2021 13:59:10 +0100 Subject: [PATCH 2/5] Do not accidentally filter out non-MemberDef trees on Scala <=2.10 --- src/main/scala/KindProjector.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/scala/KindProjector.scala b/src/main/scala/KindProjector.scala index 83c749d..00a24a3 100644 --- a/src/main/scala/KindProjector.scala +++ b/src/main/scala/KindProjector.scala @@ -375,8 +375,8 @@ class KindRewriter(plugin: Plugin, val global: Global) // Either[_, Int] case (if `underscore-placeholders` is enabled) case ExistentialTypeTree(AppliedTypeTree(t, as), params) if useUnderscoresForTypeLambda => - val nonUnderscoreExistentials = params.filterNot { - case p: MemberDef => InvPlaceholderScala3(p.name) + val nonUnderscoreExistentials = params.filter { + case p: MemberDef => !InvPlaceholderScala3(p.name) case _ => true } val nt = atPos(tree.pos.makeTransparent)(handlePlaceholders(t, as)) From 155663886214838441097cfe9ee751c9c139eaaf Mon Sep 17 00:00:00 2001 From: Kai Date: Fri, 14 May 2021 16:52:07 +0100 Subject: [PATCH 3/5] Remove 2.10 support from the build --- build.sbt | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/build.sbt b/build.sbt index 571b33b..0aceae3 100644 --- a/build.sbt +++ b/build.sbt @@ -5,7 +5,6 @@ inThisBuild { resolvers in Global += "scala-integration" at "https://scala-ci.typesafe.com/artifactory/scala-integration/", githubWorkflowPublishTargetBranches := Seq(), crossScalaVersions := Seq( - "2.10.7", "2.11.12", "2.12.8", "2.12.9", @@ -84,11 +83,6 @@ def hasNewParser(versionString: String) = versionString match { case _ => false } -def hasNewPlugin(versionString: String) = versionString match { - case HasScalaVersion(2, 10, _) => false - case _ => true -} - lazy val `kind-projector` = project .in(file(".")) .settings( @@ -105,19 +99,12 @@ lazy val `kind-projector` = project val suffices = (if (hasNewParser(sv)) "-newParser" else "-oldParser") :: (if (hasNewReporting(sv)) "-newReporting" else "-oldReporting") :: - (if (hasNewPlugin(sv)) "-newPlugin" else "-oldPlugin") :: + "-newPlugin" :: Nil suffices.map(suffix => file(dir.getPath + suffix)) } }, libraryDependencies += scalaOrganization.value % "scala-compiler" % scalaVersion.value, - libraryDependencies ++= (scalaBinaryVersion.value match { - case "2.10" => List( - compilerPlugin("org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full), - "org.scalamacros" %% "quasiquotes" % "2.1.1" - ) - case _ => Nil - }), scalacOptions ++= Seq( "-Xlint", "-feature", From 16dedbdf5427b27602a6e3f81bec9134f0ff9d13 Mon Sep 17 00:00:00 2001 From: Kai Date: Fri, 14 May 2021 16:54:01 +0100 Subject: [PATCH 4/5] Remove cruft induced by 2.10 support --- build.sbt | 1 - .../scala-newPlugin/PluginOptionsCompat.scala | 10 ---------- .../scala-oldPlugin/PluginOptionsCompat.scala | 16 ---------------- src/main/scala/KindProjector.scala | 11 ++++------- 4 files changed, 4 insertions(+), 34 deletions(-) delete mode 100644 src/main/scala-newPlugin/PluginOptionsCompat.scala delete mode 100644 src/main/scala-oldPlugin/PluginOptionsCompat.scala diff --git a/build.sbt b/build.sbt index 0aceae3..cbe168a 100644 --- a/build.sbt +++ b/build.sbt @@ -99,7 +99,6 @@ lazy val `kind-projector` = project val suffices = (if (hasNewParser(sv)) "-newParser" else "-oldParser") :: (if (hasNewReporting(sv)) "-newReporting" else "-oldReporting") :: - "-newPlugin" :: Nil suffices.map(suffix => file(dir.getPath + suffix)) } diff --git a/src/main/scala-newPlugin/PluginOptionsCompat.scala b/src/main/scala-newPlugin/PluginOptionsCompat.scala deleted file mode 100644 index 3fa91b3..0000000 --- a/src/main/scala-newPlugin/PluginOptionsCompat.scala +++ /dev/null @@ -1,10 +0,0 @@ -package d_m - -import scala.tools.nsc.plugins.Plugin - -trait PluginOptionsCompat { - def pluginOptions(plugin: Plugin) = plugin.options -} - -//compatibility stub -trait PluginCompat diff --git a/src/main/scala-oldPlugin/PluginOptionsCompat.scala b/src/main/scala-oldPlugin/PluginOptionsCompat.scala deleted file mode 100644 index f07a70a..0000000 --- a/src/main/scala-oldPlugin/PluginOptionsCompat.scala +++ /dev/null @@ -1,16 +0,0 @@ -package d_m - -import scala.tools.nsc.plugins.Plugin - -trait PluginOptionsCompat { - def pluginOptions(plugin: Plugin) = plugin.asInstanceOf[PluginCompat].options -} - -trait PluginCompat extends Plugin { - var options: List[String] = _ - override def processOptions(options: List[String], error: String => Unit): Unit = { - this.options = options - init(options, error) - } - def init(options: List[String], error: String => Unit): Boolean -} diff --git a/src/main/scala/KindProjector.scala b/src/main/scala/KindProjector.scala index 00a24a3..e602c71 100644 --- a/src/main/scala/KindProjector.scala +++ b/src/main/scala/KindProjector.scala @@ -12,7 +12,7 @@ import nsc.ast.TreeDSL import scala.reflect.NameTransformer import scala.collection.mutable -class KindProjector(val global: Global) extends Plugin with PluginCompat { +class KindProjector(val global: Global) extends Plugin { val name = "kind-projector" val description = "Expand type lambda syntax" @@ -33,7 +33,7 @@ class KindProjector(val global: Global) extends Plugin with PluginCompat { } class KindRewriter(plugin: Plugin, val global: Global) - extends PluginComponent with Transform with TypingTransformers with TreeDSL with PluginOptionsCompat with ReportingCompat { + extends PluginComponent with Transform with TypingTransformers with TreeDSL with ReportingCompat { import global._ @@ -47,7 +47,7 @@ class KindRewriter(plugin: Plugin, val global: Global) System.getProperty("kp:genAsciiNames") == "true" lazy val useUnderscoresForTypeLambda: Boolean = { - pluginOptions(plugin).contains("underscore-placeholders") + plugin.options.contains("underscore-placeholders") } def newTransformer(unit: CompilationUnit): MyTransformer = @@ -375,10 +375,7 @@ class KindRewriter(plugin: Plugin, val global: Global) // Either[_, Int] case (if `underscore-placeholders` is enabled) case ExistentialTypeTree(AppliedTypeTree(t, as), params) if useUnderscoresForTypeLambda => - val nonUnderscoreExistentials = params.filter { - case p: MemberDef => !InvPlaceholderScala3(p.name) - case _ => true - } + val nonUnderscoreExistentials = params.filter(p => !InvPlaceholderScala3(p.name)) val nt = atPos(tree.pos.makeTransparent)(handlePlaceholders(t, as)) if (nonUnderscoreExistentials.isEmpty) nt else ExistentialTypeTree(nt, nonUnderscoreExistentials) From 7e785522e83d50c47fe791585f6b62e114eb97bf Mon Sep 17 00:00:00 2001 From: Kai Date: Sat, 15 May 2021 10:18:59 +0100 Subject: [PATCH 5/5] Regenerate workflows (drop 2.10) --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7816be4..e0e05f9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,6 @@ jobs: matrix: os: [ubuntu-latest] scala: - - 2.10.7 - 2.11.12 - 2.12.8 - 2.12.9