-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Another attempt at Dotty cross-building #3486
Changes from all commits
22c3618
b874fd5
65afbac
509306b
34921b7
cc7a0c1
232cb8d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,7 +20,8 @@ isTravisBuild in Global := sys.env.get("TRAVIS").isDefined | |
|
||
val scalaCheckVersion = "1.14.3" | ||
|
||
val scalatestplusScalaCheckVersion = "3.1.2.0" | ||
val scalatestVersion = "3.2.0" | ||
val scalatestplusScalaCheckVersion = "3.2.0.0" | ||
|
||
val disciplineVersion = "1.0.2" | ||
|
||
|
@@ -43,9 +44,9 @@ def scalaVersionSpecificFolders(srcName: String, srcBaseDir: java.io.File, scala | |
List(CrossType.Pure, CrossType.Full) | ||
.flatMap(_.sharedSrcDir(srcBaseDir, srcName).toList.map(f => file(f.getPath + suffix))) | ||
CrossVersion.partialVersion(scalaVersion) match { | ||
case Some((2, y)) if y >= 13 => | ||
extraDirs("-2.13+") | ||
case _ => Nil | ||
case Some((2, y)) => extraDirs("-2.x") ++ (if (y >= 13) extraDirs("-2.13+") else Nil) | ||
case Some((0, _)) => extraDirs("-2.13+") ++ extraDirs("-3.x") | ||
case _ => Nil | ||
} | ||
} | ||
|
||
|
@@ -58,8 +59,11 @@ commonScalaVersionSettings | |
|
||
ThisBuild / mimaFailOnNoPrevious := false | ||
|
||
def doctestGenTestsDottyCompat(isDotty: Boolean, genTests: Seq[File]): Seq[File] = | ||
if (isDotty) Nil else genTests | ||
|
||
lazy val commonSettings = commonScalaVersionSettings ++ Seq( | ||
scalacOptions ++= commonScalacOptions(scalaVersion.value), | ||
scalacOptions ++= commonScalacOptions(scalaVersion.value, isDotty.value), | ||
Compile / unmanagedSourceDirectories ++= scalaVersionSpecificFolders("main", baseDirectory.value, scalaVersion.value), | ||
Test / unmanagedSourceDirectories ++= scalaVersionSpecificFolders("test", baseDirectory.value, scalaVersion.value), | ||
resolvers ++= Seq(Resolver.sonatypeRepo("releases"), Resolver.sonatypeRepo("snapshots")), | ||
|
@@ -68,19 +72,26 @@ lazy val commonSettings = commonScalaVersionSettings ++ Seq( | |
) ++ warnUnusedImport | ||
|
||
def macroDependencies(scalaVersion: String) = | ||
Seq("org.scala-lang" % "scala-reflect" % scalaVersion % Provided) | ||
if (scalaVersion.startsWith("2")) Seq("org.scala-lang" % "scala-reflect" % scalaVersion % Provided) else Nil | ||
|
||
lazy val catsSettings = Seq( | ||
incOptions := incOptions.value.withLogRecompileOnMacro(false), | ||
libraryDependencies ++= Seq( | ||
compilerPlugin(("org.typelevel" %% "kind-projector" % kindProjectorVersion).cross(CrossVersion.full)) | ||
libraryDependencies ++= ( | ||
if (isDotty.value) Nil | ||
else | ||
Seq( | ||
compilerPlugin(("org.typelevel" %% "kind-projector" % kindProjectorVersion).cross(CrossVersion.full)) | ||
) | ||
) ++ macroDependencies(scalaVersion.value) | ||
) ++ commonSettings ++ publishSettings ++ scoverageSettings ++ simulacrumSettings | ||
|
||
lazy val simulacrumSettings = Seq( | ||
addCompilerPlugin(scalafixSemanticdb), | ||
scalacOptions ++= Seq(s"-P:semanticdb:targetroot:${baseDirectory.value}/target/.semanticdb", "-Yrangepos"), | ||
libraryDependencies += "org.typelevel" %% "simulacrum-scalafix-annotations" % "0.5.0", | ||
libraryDependencies ++= (if (isDotty.value) Nil else Seq(compilerPlugin(scalafixSemanticdb))), | ||
scalacOptions ++= ( | ||
if (isDotty.value) Nil else Seq(s"-P:semanticdb:targetroot:${baseDirectory.value}/target/.semanticdb", "-Yrangepos") | ||
), | ||
libraryDependencies += | ||
("org.typelevel" %% "simulacrum-scalafix-annotations" % "0.5.0").withDottyCompat(scalaVersion.value), | ||
pomPostProcess := { (node: xml.Node) => | ||
new RuleTransformer(new RewriteRule { | ||
override def transform(node: xml.Node): Seq[xml.Node] = | ||
|
@@ -140,15 +151,25 @@ lazy val includeGeneratedSrc: Setting[_] = { | |
} | ||
|
||
lazy val disciplineDependencies = Seq( | ||
libraryDependencies ++= Seq("org.scalacheck" %%% "scalacheck" % scalaCheckVersion, | ||
"org.typelevel" %%% "discipline-core" % disciplineVersion | ||
) | ||
libraryDependencies ++= Seq( | ||
"org.scalacheck" %%% "scalacheck" % scalaCheckVersion, | ||
"org.typelevel" %%% "discipline-core" % disciplineVersion | ||
).map(_.withDottyCompat(scalaVersion.value)) | ||
) | ||
|
||
lazy val testingDependencies = Seq( | ||
libraryDependencies ++= Seq( | ||
"org.typelevel" %%% "discipline-scalatest" % disciplineScalatestVersion % Test, | ||
"org.scalatest" %%% "scalatest-shouldmatchers" % scalatestVersion % Test, | ||
"org.scalatest" %%% "scalatest-funsuite" % scalatestVersion % Test, | ||
"org.scalatestplus" %%% "scalacheck-1-14" % scalatestplusScalaCheckVersion % Test | ||
), | ||
libraryDependencies ++= Seq( | ||
("org.typelevel" %%% "discipline-scalatest" % disciplineScalatestVersion % Test) | ||
).map( | ||
_.exclude("org.scalatestplus", "scalacheck-1-14_2.13") | ||
.exclude("org.scalactic", "scalactic_2.13") | ||
.exclude("org.scalatest", "scalatest_2.13") | ||
.withDottyCompat(scalaVersion.value) | ||
Comment on lines
+166
to
+172
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm tentatively okay with merging this as-is, but only if we follow up on discipline-scalatest asap to update its dependency. Exclusion rules in published dependencies are dangerous on several levels. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What do you think about changing the exclusion to only apply for Dotty (which we’re not currently publishing anyway)? I think it’s likely I’ll be the one publishing discipline-scalatest, and I don’t really want to start doing that for every Dotty release since it’s just a test dependency. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @djspiewak Oh, nevermind, I forgot that this is only a test dependency (and that's configured here, so there's no danger of them accidentally missing an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah if it's only a test dependency then I'm okay with it for now. |
||
) | ||
) | ||
|
||
|
@@ -394,6 +415,7 @@ lazy val docs = project | |
.settings(docSettings) | ||
.settings(commonJvmSettings) | ||
.settings( | ||
crossScalaVersions := crossScalaVersions.value.init, | ||
libraryDependencies ++= Seq( | ||
"org.typelevel" %%% "discipline-scalatest" % disciplineScalatestVersion | ||
), | ||
|
@@ -486,7 +508,10 @@ lazy val kernel = crossProject(JSPlatform, JVMPlatform) | |
.settings(includeGeneratedSrc) | ||
.jsSettings(commonJsSettings) | ||
.jvmSettings(commonJvmSettings ++ mimaSettings("cats-kernel")) | ||
.settings(libraryDependencies += "org.scalacheck" %%% "scalacheck" % scalaCheckVersion % Test) | ||
.settings( | ||
libraryDependencies += ("org.scalacheck" %%% "scalacheck" % scalaCheckVersion % Test) | ||
.withDottyCompat(scalaVersion.value) | ||
) | ||
|
||
lazy val kernelLaws = crossProject(JSPlatform, JVMPlatform) | ||
.in(file("kernel-laws")) | ||
|
@@ -509,7 +534,18 @@ lazy val core = crossProject(JSPlatform, JVMPlatform) | |
.settings(catsSettings) | ||
.settings(sourceGenerators in Compile += (sourceManaged in Compile).map(Boilerplate.gen).taskValue) | ||
.settings(includeGeneratedSrc) | ||
.settings(libraryDependencies += "org.scalacheck" %%% "scalacheck" % scalaCheckVersion % Test) | ||
.settings( | ||
libraryDependencies += ("org.scalacheck" %%% "scalacheck" % scalaCheckVersion % Test) | ||
.withDottyCompat(scalaVersion.value), | ||
doctestGenTests := doctestGenTestsDottyCompat(isDotty.value, doctestGenTests.value) | ||
) | ||
.settings( | ||
scalacOptions in Compile := | ||
(scalacOptions in Compile).value.filter { | ||
case "-Xfatal-warnings" if isDotty.value => false | ||
case _ => true | ||
} | ||
) | ||
.jsSettings(commonJsSettings) | ||
.jvmSettings(commonJvmSettings ++ mimaSettings("cats-core")) | ||
|
||
|
@@ -746,7 +782,8 @@ addCommandAlias("validateJVM", ";fmtCheck;buildJVM;bench/test;validateBC;makeMic | |
addCommandAlias("validateJS", ";catsJS/compile;testsJS/test;js/test") | ||
addCommandAlias("validateKernelJS", "kernelLawsJS/test") | ||
addCommandAlias("validateFreeJS", "freeJS/test") //separated due to memory constraint on travis | ||
addCommandAlias("validate", ";clean;validateJS;validateKernelJS;validateFreeJS;validateJVM") | ||
addCommandAlias("validateDotty", ";++0.24.0!;alleycatsLawsJVM/compile") | ||
addCommandAlias("validate", ";clean;validateJS;validateKernelJS;validateFreeJS;validateJVM;validateDotty") | ||
|
||
addCommandAlias("prePR", "fmt") | ||
|
||
|
@@ -773,29 +810,33 @@ lazy val crossVersionSharedSources: Seq[Setting[_]] = | |
} | ||
} | ||
|
||
def commonScalacOptions(scalaVersion: String) = | ||
def commonScalacOptions(scalaVersion: String, isDotty: Boolean) = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As an fyi, the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right, I just thought this was clearer than trying to figure out and replicate whatever logic sbt-dotty uses for |
||
Seq( | ||
"-encoding", | ||
"UTF-8", | ||
"-feature", | ||
"-language:existentials", | ||
"-language:higherKinds", | ||
"-language:implicitConversions", | ||
"-unchecked", | ||
"-Ywarn-dead-code", | ||
"-Ywarn-numeric-widen", | ||
"-Ywarn-value-discard", | ||
"-Xfatal-warnings", | ||
"-deprecation", | ||
"-Xlint:-unused,_" | ||
"-deprecation" | ||
) ++ (if (priorTo2_13(scalaVersion)) | ||
Seq( | ||
"-Yno-adapted-args", | ||
"-Ypartial-unification", | ||
"-Xfuture" | ||
) | ||
else | ||
Nil) | ||
Nil) ++ (if (isDotty) | ||
Seq("-language:implicitConversions", "-Ykind-projector", "-Xignore-scala2-macros") | ||
else | ||
Seq( | ||
"-language:existentials", | ||
"-language:higherKinds", | ||
"-language:implicitConversions", | ||
"-Ywarn-dead-code", | ||
"-Ywarn-numeric-widen", | ||
"-Ywarn-value-discard", | ||
"-Xlint:-unused,_" | ||
)) | ||
|
||
def priorTo2_13(scalaVersion: String): Boolean = | ||
CrossVersion.partialVersion(scalaVersion) match { | ||
|
@@ -836,7 +877,7 @@ lazy val sharedReleaseProcess = Seq( | |
) | ||
|
||
lazy val warnUnusedImport = Seq( | ||
scalacOptions ++= Seq("-Ywarn-unused:imports"), | ||
scalacOptions ++= (if (isDotty.value) Nil else Seq("-Ywarn-unused:imports")), | ||
scalacOptions in (Compile, console) ~= { _.filterNot(Set("-Ywarn-unused-import", "-Ywarn-unused:imports")) }, | ||
scalacOptions in (Test, console) := (scalacOptions in (Compile, console)).value | ||
) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
package cats | ||
package arrow | ||
|
||
import scala.language.experimental.macros | ||
import scala.reflect.macros.blackbox.Context | ||
|
||
private[arrow] class FunctionKMacroMethods { | ||
|
||
/** | ||
* Lifts function `f` of `F[A] => G[A]` into a `FunctionK[F, G]`. | ||
* | ||
* {{{ | ||
* def headOption[A](list: List[A]): Option[A] = list.headOption | ||
* val lifted: FunctionK[List, Option] = FunctionK.lift(headOption) | ||
* }}} | ||
* | ||
* Note: This method has a macro implementation that returns a new | ||
* `FunctionK` instance as follows: | ||
* | ||
* {{{ | ||
* new FunctionK[F, G] { | ||
* def apply[A](fa: F[A]): G[A] = f(fa) | ||
* } | ||
* }}} | ||
* | ||
* Additionally, the type parameters on `f` must not be specified. | ||
*/ | ||
def lift[F[_], G[_]](f: (F[α] => G[α]) forSome { type α }): FunctionK[F, G] = | ||
macro FunctionKMacros.lift[F, G] | ||
} | ||
|
||
private[arrow] object FunctionKMacros { | ||
|
||
def lift[F[_], G[_]](c: Context)( | ||
f: c.Expr[(F[α] => G[α]) forSome { type α }] | ||
)(implicit | ||
evF: c.WeakTypeTag[F[_]], | ||
evG: c.WeakTypeTag[G[_]] | ||
): c.Expr[FunctionK[F, G]] = | ||
c.Expr[FunctionK[F, G]](new Lifter[c.type](c).lift[F, G](f.tree)) | ||
// ^^note: extra space after c.type to appease scalastyle | ||
|
||
private[this] class Lifter[C <: Context](val c: C) { | ||
import c.universe._ | ||
|
||
def lift[F[_], G[_]](tree: Tree)(implicit | ||
evF: c.WeakTypeTag[F[_]], | ||
evG: c.WeakTypeTag[G[_]] | ||
): Tree = | ||
unblock(tree) match { | ||
case q"($param) => $trans[..$typeArgs](${arg: Ident})" if param.name == arg.name => | ||
typeArgs | ||
.collect { case tt: TypeTree => tt } | ||
.find(tt => Option(tt.original).isDefined) | ||
.foreach { param => | ||
c.abort(param.pos, | ||
s"type parameter $param must not be supplied when lifting function $trans to FunctionK" | ||
) | ||
} | ||
|
||
val F = punchHole(evF.tpe) | ||
val G = punchHole(evG.tpe) | ||
|
||
q""" | ||
new _root_.cats.arrow.FunctionK[$F, $G] { | ||
def apply[A](fa: $F[A]): $G[A] = $trans(fa) | ||
} | ||
""" | ||
case other => | ||
c.abort(other.pos, s"Unexpected tree $other when lifting to FunctionK") | ||
} | ||
|
||
private[this] def unblock(tree: Tree): Tree = | ||
tree match { | ||
case Block(Nil, expr) => expr | ||
case _ => tree | ||
} | ||
|
||
private[this] def punchHole(tpe: Type): Tree = | ||
tpe match { | ||
case PolyType(undet :: Nil, underlying: TypeRef) => | ||
val α = TypeName("α") | ||
def rebind(typeRef: TypeRef): Tree = | ||
if (typeRef.sym == undet) tq"$α" | ||
else { | ||
val args = typeRef.args.map { | ||
case ref: TypeRef => rebind(ref) | ||
case arg => tq"$arg" | ||
} | ||
tq"${typeRef.sym}[..$args]" | ||
} | ||
val rebound = rebind(underlying) | ||
tq"""({type λ[$α] = $rebound})#λ""" | ||
case TypeRef(pre, sym, Nil) => | ||
tq"$sym" | ||
case _ => | ||
c.abort(c.enclosingPosition, s"Unexpected type $tpe when lifting to FunctionK") | ||
} | ||
|
||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
package cats | ||
package arrow | ||
|
||
private[arrow] class FunctionKMacroMethods |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this have to be in the runtime classpath?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, this isn't new to this PR, but it's a good point—I'll change this to
Provided
before 2.2.0, and make sure that we're doing the same thing with Simulacrum Scalafix as we were with Simulacrum (the macro annotation version) in this respect. But I think that should be a follow-up.