From 1a13a4308847cbfab742ccbe3c9919fd3fa47b9c Mon Sep 17 00:00:00 2001 From: Jacob Wang Date: Thu, 10 Jun 2021 23:58:25 +0100 Subject: [PATCH 1/6] Cross build for scala 3 --- build.sbt | 22 ++++++++++++------- .../internal/ConfigureMethods.scala | 8 +++++++ .../difflicious/utils/TypeNamePlatform.scala | 0 3 files changed, 22 insertions(+), 8 deletions(-) create mode 100644 modules/core/src/main/scala-3/difflicious/internal/ConfigureMethods.scala create mode 100644 modules/core/src/main/scala-3/difflicious/utils/TypeNamePlatform.scala diff --git a/build.sbt b/build.sbt index 5d01a00..045e558 100644 --- a/build.sbt +++ b/build.sbt @@ -5,10 +5,15 @@ val scalatestVersion = "3.2.9" val scala213 = "2.13.6" val scala3 = "3.0.0" +val isScala3 = Def.setting { + // doesn't work well with >= 3.0.0 for `3.0.0-M1` + scalaVersion.value.startsWith("3") +} + inThisBuild( List( - scalaVersion := scala213, - crossScalaVersions := Seq(scala213 /*, scala3*/ ), + scalaVersion := scala3, // FIXME: + crossScalaVersions := Seq(scala213, scala3), organization := "com.github.jatcwang", homepage := Some(url("https://github.com/jatcwang/difflicious")), licenses := List("Apache-2.0" -> url("http://www.apache.org/licenses/LICENSE-2.0")), @@ -31,9 +36,10 @@ lazy val core = Project("difflicious-core", file("modules/core")) .settings(commonSettings) .settings( libraryDependencies ++= Seq( - "com.propensive" %% "magnolia" % "0.17.0", - "dev.zio" %% "izumi-reflect" % "1.1.1", - "com.lihaoyi" %% "fansi" % "0.2.12", + if (isScala3.value) "com.softwaremill.magnolia" %% "magnolia-core" % "2.0.0-M6" + else "com.propensive" %% "magnolia" % "0.17.0", + "dev.zio" %% "izumi-reflect" % "1.1.2", + "com.lihaoyi" %% "fansi" % "0.2.14", ) ++ ( if (scalaVersion.value.startsWith("2")) Seq("org.scala-lang" % "scala-reflect" % "2.13.5") @@ -152,9 +158,9 @@ lazy val commonSettings = Seq( versionScheme := Some("early-semver"), scalacOptions ++= Seq("-Wmacros:after"), libraryDependencies ++= Seq( - compilerPlugin("org.typelevel" %% "kind-projector" % "0.13.0" cross CrossVersion.full), - compilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1"), - ).filterNot(_ => scalaVersion.value.startsWith("3")), + compilerPlugin("org.typelevel" %% "kind-projector" % "0.13.0" cross CrossVersion.full), + compilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1"), + ).filterNot(_ => isScala3.value), ) lazy val noPublishSettings = Seq( diff --git a/modules/core/src/main/scala-3/difflicious/internal/ConfigureMethods.scala b/modules/core/src/main/scala-3/difflicious/internal/ConfigureMethods.scala new file mode 100644 index 0000000..53d62d9 --- /dev/null +++ b/modules/core/src/main/scala-3/difflicious/internal/ConfigureMethods.scala @@ -0,0 +1,8 @@ +trait ConfigureMethods[T]: + this => Differ[T] + + def ignoreAt[U](path: T => U): Differ[T] + + def configure[U](path: T => U)(configFunc: Differ[U] => Differ[U]): Differ[T] = + + def replace[U](path: T => U)(newDiffer: Differ[U]): Differ[T] = diff --git a/modules/core/src/main/scala-3/difflicious/utils/TypeNamePlatform.scala b/modules/core/src/main/scala-3/difflicious/utils/TypeNamePlatform.scala new file mode 100644 index 0000000..e69de29 From c97a5a7dd77be5236413c65d0abac35597e5e029 Mon Sep 17 00:00:00 2001 From: Jacob Wang Date: Sat, 24 Jul 2021 23:37:02 +0100 Subject: [PATCH 2/6] xxx --- .gitignore | 1 + .jvmopts | 1 + build.sbt | 18 +-- .../difflicious/DifferGen.scala | 22 ++-- .../internal/ConfigureMethods.scala | 21 +-- .../main/scala-3/difflicious/DifferGen.scala | 120 ++++++++++++++++++ .../internal/ConfigureMethods.scala | 13 +- .../difflicious/utils/TypeNamePlatform.scala | 5 + .../difflicious/differ/RecordDiffer.scala | 24 ++-- .../difflicious/internal/PairByOps.scala | 22 ++++ .../ScalaVersionDependentTestTypes.scala | 31 +++++ .../ScalaVersionDependentTests.scala | 25 ++++ .../ScalaVersionDependentTestTypes.scala | 4 + .../ScalaVersionDependentTests.scala | 5 + .../difflicious/DifferConfigureSpec.scala | 19 +-- .../test/scala/difflicious/DifferSpec.scala | 34 ++--- .../test/scala/difflicious/testtypes.scala | 44 +++---- .../scala/difflicious/testutils/package.scala | 2 +- .../difflicious/scalatest/ScalatestDiff.scala | 0 .../difflicious.scalatest/ScalatestDiff.scala | 16 +++ project/plugins.sbt | 1 + 21 files changed, 316 insertions(+), 112 deletions(-) create mode 100644 .jvmopts rename modules/core/src/main/{scala => scala-2.13}/difflicious/DifferGen.scala (88%) create mode 100644 modules/core/src/main/scala-3/difflicious/DifferGen.scala create mode 100644 modules/core/src/main/scala/difflicious/internal/PairByOps.scala create mode 100644 modules/coretest/src/test/scala-2.13/difflicious/ScalaVersionDependentTestTypes.scala create mode 100644 modules/coretest/src/test/scala-2.13/difflicious/ScalaVersionDependentTests.scala create mode 100644 modules/coretest/src/test/scala-3/difflicious/ScalaVersionDependentTestTypes.scala create mode 100644 modules/coretest/src/test/scala-3/difflicious/ScalaVersionDependentTests.scala rename modules/scalatest/src/main/{scala => scala-2.13}/difflicious/scalatest/ScalatestDiff.scala (100%) create mode 100644 modules/scalatest/src/main/scala-3/difflicious.scalatest/ScalatestDiff.scala diff --git a/.gitignore b/.gitignore index f16e6d6..882c989 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,4 @@ RUNNING_PID metals.sbt .bsp TempGo.scala +metals.sbt diff --git a/.jvmopts b/.jvmopts new file mode 100644 index 0000000..e2a2121 --- /dev/null +++ b/.jvmopts @@ -0,0 +1 @@ +-Xmx3G diff --git a/build.sbt b/build.sbt index 045e558..e6a0b88 100644 --- a/build.sbt +++ b/build.sbt @@ -3,7 +3,7 @@ val catsVersion = "2.6.1" val scalatestVersion = "3.2.9" val scala213 = "2.13.6" -val scala3 = "3.0.0" +val scala3 = "3.0.1" val isScala3 = Def.setting { // doesn't work well with >= 3.0.0 for `3.0.0-M1` @@ -36,10 +36,11 @@ lazy val core = Project("difflicious-core", file("modules/core")) .settings(commonSettings) .settings( libraryDependencies ++= Seq( - if (isScala3.value) "com.softwaremill.magnolia" %% "magnolia-core" % "2.0.0-M6" - else "com.propensive" %% "magnolia" % "0.17.0", - "dev.zio" %% "izumi-reflect" % "1.1.2", - "com.lihaoyi" %% "fansi" % "0.2.14", + if (isScala3.value) "com.softwaremill.magnolia1_3" %% "magnolia" % "1.0.0-M4" +// if (isScala3.value) "com.softwaremill.magnolia" %% "magnolia-core" % "2.0.0-M7-SNAPSHOT" + else "com.softwaremill.magnolia" %% "magnolia-core" % "1.0.0-M4", + "dev.zio" %% "izumi-reflect" % "1.1.2", + "com.lihaoyi" %% "fansi" % "0.2.14", ) ++ ( if (scalaVersion.value.startsWith("2")) Seq("org.scala-lang" % "scala-reflect" % "2.13.5") @@ -107,6 +108,7 @@ lazy val docs = project .settings( libraryDependencies ++= Seq( "org.scalatest" %% "scalatest" % scalatestVersion, + "org.scalameta" %% "mdoc" % "2.2.21", ), ) .settings( @@ -156,10 +158,10 @@ lazy val commonSettings = Seq( } }, versionScheme := Some("early-semver"), - scalacOptions ++= Seq("-Wmacros:after"), + scalacOptions ++= (if (isScala3.value) Seq.empty[String] else Seq("-Wmacros:after")), libraryDependencies ++= Seq( - compilerPlugin("org.typelevel" %% "kind-projector" % "0.13.0" cross CrossVersion.full), - compilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1"), + compilerPlugin("org.typelevel" %% "kind-projector" % "0.13.0" cross CrossVersion.full), + compilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1"), ).filterNot(_ => isScala3.value), ) diff --git a/modules/core/src/main/scala/difflicious/DifferGen.scala b/modules/core/src/main/scala-2.13/difflicious/DifferGen.scala similarity index 88% rename from modules/core/src/main/scala/difflicious/DifferGen.scala rename to modules/core/src/main/scala-2.13/difflicious/DifferGen.scala index 4729b57..36114ff 100644 --- a/modules/core/src/main/scala/difflicious/DifferGen.scala +++ b/modules/core/src/main/scala-2.13/difflicious/DifferGen.scala @@ -3,8 +3,7 @@ import difflicious.DiffResult.MismatchTypeResult import difflicious.differ.RecordDiffer import difflicious.internal.EitherGetSyntax._ import difflicious.utils.TypeName.SomeTypeName -import magnolia.{TypeName => MTypeName, _} -import difflicious.utils._ +import magnolia._ import scala.collection.mutable import scala.collection.immutable.ListMap @@ -45,15 +44,18 @@ trait DifferGen { case DiffInput.ExpectedOnly(expected) => ctx.dispatch(expected)(sub => sub.typeclass.diff(DiffInput.ExpectedOnly(sub.cast(expected)))) case DiffInput.Both(obtained, expected) => { - ctx.dispatch(obtained) { actualSubtype => + ctx.dispatch(obtained) { obtainedSubtype => ctx.dispatch(expected) { expectedSubtype => - if (actualSubtype.typeName.short == expectedSubtype.typeName.short) { - actualSubtype.typeclass - .diff(actualSubtype.cast(obtained), expectedSubtype.cast(expected).asInstanceOf[actualSubtype.SType]) + if (obtainedSubtype.typeName.short == expectedSubtype.typeName.short) { + obtainedSubtype.typeclass + .diff( + obtainedSubtype.cast(obtained), + expectedSubtype.cast(expected).asInstanceOf[obtainedSubtype.SType] + ) } else { MismatchTypeResult( - obtained = actualSubtype.typeclass.diff(DiffInput.ObtainedOnly(actualSubtype.cast(obtained))), - obtainedTypeName = toDiffliciousTypeName(actualSubtype.typeName), + obtained = obtainedSubtype.typeclass.diff(DiffInput.ObtainedOnly(obtainedSubtype.cast(obtained))), + obtainedTypeName = toDiffliciousTypeName(obtainedSubtype.typeName), expected = expectedSubtype.typeclass.diff(DiffInput.ExpectedOnly(expectedSubtype.cast(expected))), expectedTypeName = toDiffliciousTypeName(expectedSubtype.typeName), pairType = PairType.Both, @@ -132,8 +134,8 @@ trait DifferGen { def derived[T]: Differ[T] = macro Magnolia.gen[T] - private def toDiffliciousTypeName(typeName: MTypeName): SomeTypeName = { - TypeName( + private def toDiffliciousTypeName(typeName: magnolia.TypeName): SomeTypeName = { + difflicious.utils.TypeName( long = typeName.full, short = typeName.short, typeArguments = typeName.typeArguments diff --git a/modules/core/src/main/scala-2.13/difflicious/internal/ConfigureMethods.scala b/modules/core/src/main/scala-2.13/difflicious/internal/ConfigureMethods.scala index 5ae103d..4b1e975 100644 --- a/modules/core/src/main/scala-2.13/difflicious/internal/ConfigureMethods.scala +++ b/modules/core/src/main/scala-2.13/difflicious/internal/ConfigureMethods.scala @@ -1,12 +1,9 @@ package difflicious.internal -import difflicious.ConfigureOp.PairBy -import difflicious.internal.EitherGetSyntax.EitherExtensionOps -import difflicious.{ConfigurePath, Differ} -import difflicious.utils.Pairable +import difflicious.Differ import scala.collection.mutable -import scala.annotation.{tailrec, nowarn} +import scala.annotation.{nowarn, tailrec} import scala.reflect.macros.blackbox trait ConfigureMethods[T] { this: Differ[T] => @@ -20,20 +17,6 @@ trait ConfigureMethods[T] { this: Differ[T] => macro ConfigureMacro.replace_impl[T, U] } -// pairBy has to be defined differently for better type inference. -final class PairByOps[F[_], A](differ: Differ[F[A]]) { - def pairBy[B](f: A => B): Differ[F[A]] = - differ.configureRaw(ConfigurePath.current, PairBy.ByFunc(f)).unsafeGet - - def pairByIndex: Differ[F[A]] = - differ.configureRaw(ConfigurePath.current, PairBy.Index).unsafeGet -} - -trait ToPairByOps { - @nowarn("msg=.*never used.*") - implicit def toPairByOps[F[_]: Pairable, A](differ: Differ[F[A]]): PairByOps[F, A] = new PairByOps(differ) -} - // Implementation inspired by quicklen's path macro. // See https://github.com/softwaremill/quicklens/blob/c2fd335b80f3d4d55a76d146d8308d95575dd749/quicklens/src/main/scala-2/com/softwaremill/quicklens/QuicklensMacros.scala object ConfigureMacro { diff --git a/modules/core/src/main/scala-3/difflicious/DifferGen.scala b/modules/core/src/main/scala-3/difflicious/DifferGen.scala new file mode 100644 index 0000000..d518c1d --- /dev/null +++ b/modules/core/src/main/scala-3/difflicious/DifferGen.scala @@ -0,0 +1,120 @@ +package difflicious +import difflicious.DiffResult.MismatchTypeResult +import difflicious.differ.RecordDiffer +import difflicious.utils.TypeName +import difflicious.utils.TypeName.SomeTypeName +import difflicious.DiffResult +import difflicious.internal.EitherGetSyntax._ + +import scala.collection.immutable.ListMap +import magnolia1._ + +import scala.collection.mutable + +trait DifferGen extends Derivation[Differ]: + override def join[T](ctx: CaseClass[Differ, T]): Differ[T] = + new RecordDiffer[T]( + ctx.params.map { p => + val getter = p.deref + p.label -> Tuple2(getter.asInstanceOf[(T => Any)], p.typeclass.asInstanceOf[Differ[Any]]) + }.to(ListMap), + isIgnored = false, + typeName = toDiffliciousTypeName(ctx.typeInfo) + ) + + override def split[T](ctx: SealedTrait[Differ, T]): Differ[T] = + new SealedTraitDiffer(ctx, isIgnored = false) + + final class SealedTraitDiffer[T](ctx: SealedTrait[Differ, T], isIgnored: Boolean) extends Differ[T]: + override type R = DiffResult + + override def diff(inputs: DiffInput[T]): DiffResult = inputs match + case DiffInput.ObtainedOnly(obtained) => + ctx.choose(obtained)(sub => sub.typeclass.diff(DiffInput.ObtainedOnly(sub.cast(obtained)))) + case DiffInput.ExpectedOnly(expected) => + ctx.choose(expected)(sub => sub.typeclass.diff(DiffInput.ExpectedOnly(sub.cast(expected)))) + case DiffInput.Both(obtained, expected) => + ctx.choose(obtained) { obtainedSubtype => + ctx.choose(expected) { expectedSubtype => + if obtainedSubtype.typeInfo.short == expectedSubtype.typeInfo.short then + obtainedSubtype.typeclass.asInstanceOf[Differ[T]].diff(obtainedSubtype.value, expectedSubtype.value) + else MismatchTypeResult( + obtained = obtainedSubtype.typeclass.diff(DiffInput.ObtainedOnly(obtainedSubtype.cast(obtained))), + obtainedTypeName = toDiffliciousTypeName(obtainedSubtype.typeInfo), + expected = expectedSubtype.typeclass.diff(DiffInput.ExpectedOnly(expectedSubtype.cast(expected))), + expectedTypeName = toDiffliciousTypeName(expectedSubtype.typeInfo), + pairType = PairType.Both, + isIgnored = isIgnored, + ) + } + } + + + override def configureIgnored(newIgnored: Boolean): Differ[T] = + val newSubtypes = mutable.ArrayBuffer.empty[SealedTrait.Subtype[Differ, T, Any]] + ctx.subtypes.map { sub => + newSubtypes += SealedTrait.Subtype[Differ, T, Any]( + typeInfo = sub.typeInfo, + annotations = sub.annotations, + typeAnnotations = sub.typeAnnotations, + isObject = sub.isObject, + index = sub.index, + callByNeed = + CallByNeed(sub.typeclass.configureRaw(ConfigurePath.current, ConfigureOp.SetIgnored(newIgnored)).unsafeGet.asInstanceOf[Differ[Any]]), + isType = sub.cast.isDefinedAt, + asType = sub.cast.apply, + ) + } + val newSealedTrait = new SealedTrait( + typeInfo = ctx.typeInfo, + subtypes = IArray(newSubtypes.toArray: _*), + annotations = ctx.annotations, + typeAnnotations = ctx.typeAnnotations, + ) + new SealedTraitDiffer[T](newSealedTrait, isIgnored = newIgnored) + + protected def configurePath( + step: String, + nextPath: ConfigurePath, + op: ConfigureOp + ): Either[ConfigureError, Differ[T]] = + ctx.subtypes.zipWithIndex.find{ (sub, _) => sub.typeInfo.short == step} match { + case Some((sub, idx)) => + sub.typeclass + .configureRaw(nextPath, op) + .map { newDiffer => + val newSubtype = SealedTrait.Subtype[Differ, T, Any]( + typeInfo = sub.typeInfo, + annotations = sub.annotations, + typeAnnotations = sub.typeAnnotations, + isObject = sub.isObject, + index = sub.index, + callByNeed = CallByNeed(newDiffer.asInstanceOf[Differ[Any]]), + isType = sub.cast.isDefinedAt, + asType = sub.cast.apply, + ) + val newSubtypes = ctx.subtypes.updated(idx, newSubtype) + val newSealedTrait = new SealedTrait( + typeInfo = ctx.typeInfo, + subtypes = newSubtypes, + annotations = ctx.annotations, + typeAnnotations = ctx.typeAnnotations, + ) + new SealedTraitDiffer[T](newSealedTrait, isIgnored) + } + case None => + Left(ConfigureError.UnrecognizedSubType(nextPath, ctx.subtypes.map(_.typeInfo.short).toVector)) + } + + protected def configurePairBy(path: ConfigurePath, op: ConfigureOp.PairBy[_]): Either[ConfigureError, Differ[T]] = + Left(ConfigureError.InvalidConfigureOp(path, op, "SealedTraitDiffer")) + + end SealedTraitDiffer + + private def toDiffliciousTypeName(typeInfo: TypeInfo): SomeTypeName = { + TypeName( + long = s"${typeInfo.owner}.${typeInfo.short}", + short = typeInfo.short, + typeArguments = typeInfo.typeParams.map(toDiffliciousTypeName).toList + ) + } diff --git a/modules/core/src/main/scala-3/difflicious/internal/ConfigureMethods.scala b/modules/core/src/main/scala-3/difflicious/internal/ConfigureMethods.scala index 53d62d9..f7feb6c 100644 --- a/modules/core/src/main/scala-3/difflicious/internal/ConfigureMethods.scala +++ b/modules/core/src/main/scala-3/difflicious/internal/ConfigureMethods.scala @@ -1,8 +1,13 @@ +package difflicious.internal +import difflicious.Differ +import scala.annotation.nowarn + trait ConfigureMethods[T]: - this => Differ[T] + this: Differ[T] => + + inline def ignoreAt[U](path: T => U): Differ[T] = ??? // FIXME: - def ignoreAt[U](path: T => U): Differ[T] + inline def configure[U](path: T => U)(configFunc: Differ[U] => Differ[U]): Differ[T] = ??? // FIXME: - def configure[U](path: T => U)(configFunc: Differ[U] => Differ[U]): Differ[T] = + inline def replace[U](path: T => U)(newDiffer: Differ[U]): Differ[T] = ??? // FIXME: - def replace[U](path: T => U)(newDiffer: Differ[U]): Differ[T] = diff --git a/modules/core/src/main/scala-3/difflicious/utils/TypeNamePlatform.scala b/modules/core/src/main/scala-3/difflicious/utils/TypeNamePlatform.scala index e69de29..faf2b63 100644 --- a/modules/core/src/main/scala-3/difflicious/utils/TypeNamePlatform.scala +++ b/modules/core/src/main/scala-3/difflicious/utils/TypeNamePlatform.scala @@ -0,0 +1,5 @@ +package difflicious.utils + +trait TypeNamePlatform { + def unescapeIdentifierName(str: String): String = ??? +} diff --git a/modules/core/src/main/scala/difflicious/differ/RecordDiffer.scala b/modules/core/src/main/scala/difflicious/differ/RecordDiffer.scala index 20ee5f0..f87a37f 100644 --- a/modules/core/src/main/scala/difflicious/differ/RecordDiffer.scala +++ b/modules/core/src/main/scala/difflicious/differ/RecordDiffer.scala @@ -79,17 +79,19 @@ final class RecordDiffer[T]( nextPath: ConfigurePath, op: ConfigureOp, ): Either[ConfigureError, Differ[T]] = - for { - (getter, fieldDiffer) <- fieldDiffers - .get(step) - .toRight(ConfigureError.NonExistentField(nextPath, "RecordDiffer")) - newFieldDiffer <- fieldDiffer.configureRaw(nextPath, op) - } yield new RecordDiffer[T]( - fieldDiffers = fieldDiffers.updated(step, (getter, newFieldDiffer)), - isIgnored = isIgnored, - typeName = typeName, - ) - + fieldDiffers + .get(step) + .toRight(ConfigureError.NonExistentField(nextPath, "RecordDiffer")) + .flatMap { + case (getter, fieldDiffer) => + fieldDiffer.configureRaw(nextPath, op).map { newFieldDiffer => + new RecordDiffer[T]( + fieldDiffers = fieldDiffers.updated(step, (getter, newFieldDiffer)), + isIgnored = isIgnored, + typeName = typeName, + ) + } + } override def configurePairBy(path: ConfigurePath, op: ConfigureOp.PairBy[_]): Either[ConfigureError, Differ[T]] = Left(ConfigureError.InvalidConfigureOp(path, op, "RecordDiffer")) } diff --git a/modules/core/src/main/scala/difflicious/internal/PairByOps.scala b/modules/core/src/main/scala/difflicious/internal/PairByOps.scala new file mode 100644 index 0000000..54a3b4e --- /dev/null +++ b/modules/core/src/main/scala/difflicious/internal/PairByOps.scala @@ -0,0 +1,22 @@ +package difflicious.internal + +import difflicious.utils.Pairable +import difflicious.{ConfigurePath, Differ} +import difflicious.ConfigureOp.PairBy +import difflicious.internal.EitherGetSyntax.EitherExtensionOps + +import scala.annotation.nowarn + +// pairBy has to be defined differently for better type inference. +final class PairByOps[F[_], A](differ: Differ[F[A]]) { + def pairBy[B](f: A => B): Differ[F[A]] = + differ.configureRaw(ConfigurePath.current, PairBy.ByFunc(f)).unsafeGet + + def pairByIndex: Differ[F[A]] = + differ.configureRaw(ConfigurePath.current, PairBy.Index).unsafeGet +} + +trait ToPairByOps { + @nowarn("msg=.*never used.*") + implicit def toPairByOps[F[_]: Pairable, A](differ: Differ[F[A]]): PairByOps[F, A] = new PairByOps(differ) +} diff --git a/modules/coretest/src/test/scala-2.13/difflicious/ScalaVersionDependentTestTypes.scala b/modules/coretest/src/test/scala-2.13/difflicious/ScalaVersionDependentTestTypes.scala new file mode 100644 index 0000000..0b3bb54 --- /dev/null +++ b/modules/coretest/src/test/scala-2.13/difflicious/ScalaVersionDependentTestTypes.scala @@ -0,0 +1,31 @@ +package difflicious + +import difflicious.Differ +import difflicious.testtypes.SubSealed.{SubSub1, SubSub2} +import org.scalacheck.{Arbitrary, Gen} +import difflicious.testtypes.{Sub1, CC} + +trait ScalaVersionDependentTestTypes { + sealed trait SealedNested + + object SealedNested { + case class SubFoo(i: Int) extends SealedNested + + sealed trait SubSealed extends SealedNested + object SubSealed { + case class SubSub1(d: Double) extends SubSealed + case class SubSub2(s: String) extends SubSealed + } + + import SubSealed._ + implicit val arb: Arbitrary[SealedNested] = { + val sub1 = Gen.posNum[Int].map(SubFoo.apply) + val subsub1 = Gen.posNum[Double].map(SubSub1.apply) + val subsub2 = Gen.alphaStr.map(SubSub2.apply) + + Arbitrary(Gen.oneOf(sub1, subsub1, subsub2)) + } + implicit val differ: Differ[SealedNested] = Differ.derived[SealedNested] + } + +} diff --git a/modules/coretest/src/test/scala-2.13/difflicious/ScalaVersionDependentTests.scala b/modules/coretest/src/test/scala-2.13/difflicious/ScalaVersionDependentTests.scala new file mode 100644 index 0000000..322efd0 --- /dev/null +++ b/modules/coretest/src/test/scala-2.13/difflicious/ScalaVersionDependentTests.scala @@ -0,0 +1,25 @@ +package difflicious + +import difflicious.testtypes._ +import difflicious.testutils._ +import munit.FunSuite + +trait ScalaVersionDependentTests { this: FunSuite => + test("configure path's subType handles multi-level hierarchies") { + assertConsoleDiffOutput( + Differ[List[Sealed]].ignoreAt(_.each.subType[SubSealed.SubSub1].d), + List( + SubSealed.SubSub1(1.0), + ), + List( + SubSealed.SubSub1(2.0), + ), + s"""List( + | SubSub1( + | d: $grayIgnoredStr, + | ), + |)""".stripMargin, + ) + } + +} diff --git a/modules/coretest/src/test/scala-3/difflicious/ScalaVersionDependentTestTypes.scala b/modules/coretest/src/test/scala-3/difflicious/ScalaVersionDependentTestTypes.scala new file mode 100644 index 0000000..f94ea3e --- /dev/null +++ b/modules/coretest/src/test/scala-3/difflicious/ScalaVersionDependentTestTypes.scala @@ -0,0 +1,4 @@ +package difflicious + +trait ScalaVersionDependentTestTypes: + val x=1 diff --git a/modules/coretest/src/test/scala-3/difflicious/ScalaVersionDependentTests.scala b/modules/coretest/src/test/scala-3/difflicious/ScalaVersionDependentTests.scala new file mode 100644 index 0000000..3179aa8 --- /dev/null +++ b/modules/coretest/src/test/scala-3/difflicious/ScalaVersionDependentTests.scala @@ -0,0 +1,5 @@ +package difflicious + +trait ScalaVersionDependentTests {// self: munit.FunSuite => + +} diff --git a/modules/coretest/src/test/scala/difflicious/DifferConfigureSpec.scala b/modules/coretest/src/test/scala/difflicious/DifferConfigureSpec.scala index 0510505..1f6ff21 100644 --- a/modules/coretest/src/test/scala/difflicious/DifferConfigureSpec.scala +++ b/modules/coretest/src/test/scala/difflicious/DifferConfigureSpec.scala @@ -36,23 +36,6 @@ class DifferConfigureSpec extends munit.FunSuite { assertEquals(firstLine, "Specified subtype is not a known direct subtype of trait OpenSuperType.") } - test("configure path's subType handles multi-level hierarchies") { - assertConsoleDiffOutput( - Differ[List[Sealed]].ignoreAt(_.each.subType[SubSealed.SubSub1].d), - List( - SubSealed.SubSub1(1.0), - ), - List( - SubSealed.SubSub1(2.0), - ), - s"""List( - | SubSub1( - | d: $grayIgnoredStr, - | ), - |)""".stripMargin, - ) - } - test("configure path allows 'each' to resolve underlying differ in a Map") { assertConsoleDiffOutput( Differ[Map[String, CC]].ignoreAt(_.each.dd), @@ -71,6 +54,7 @@ class DifferConfigureSpec extends munit.FunSuite { |)""".stripMargin, ) } + test("configure path allows 'each' to resolve underlying differ in a Seq") { assertConsoleDiffOutput( Differ[List[CC]].ignoreAt(_.each.dd), @@ -110,6 +94,7 @@ class DifferConfigureSpec extends munit.FunSuite { } test("configure path can handle escaped sub-type and field names") { + import Sealed.`Weird@Sub` assertConsoleDiffOutput( Differ[List[Sealed]].ignoreAt(_.each.subType[`Weird@Sub`].`weird@Field`), List( diff --git a/modules/coretest/src/test/scala/difflicious/DifferSpec.scala b/modules/coretest/src/test/scala/difflicious/DifferSpec.scala index 2b4fa79..5cd5dec 100644 --- a/modules/coretest/src/test/scala/difflicious/DifferSpec.scala +++ b/modules/coretest/src/test/scala/difflicious/DifferSpec.scala @@ -9,7 +9,7 @@ import difflicious.internal.EitherGetSyntax._ import scala.collection.immutable.HashSet -class DifferSpec extends ScalaCheckSuite { +class DifferSpec extends ScalaCheckSuite with ScalaVersionDependentTests { test("NumericDiffer: configure fails if path is not terminal") { assertEquals( Differ[Int].configureRaw(ConfigurePath.of("nono"), ConfigureOp.ignore), @@ -647,15 +647,15 @@ class DifferSpec extends ScalaCheckSuite { test("Sealed trait: should display obtained and expected types when mismatch") { assertConsoleDiffOutput( Sealed.differ, - Sub1(1), - SubSealed.SubSub1(1), - s"""${R}Sub1$X != ${G}SubSub1$X + Sealed.Sub1(1), + Sealed.Sub2(1.0), + s"""${R}Sub1$X != ${G}Sub2${X} |${R}=== Obtained === |Sub1( | i: 1, |)$X |$G=== Expected === - |SubSub1( + |Sub2( | d: 1.0, |)$X""".stripMargin, ) @@ -664,15 +664,15 @@ class DifferSpec extends ScalaCheckSuite { test("Sealed trait: should display obtained and expected types when mismatch") { assertConsoleDiffOutput( Sealed.differ, - Sub1(1), - SubSealed.SubSub1(1), - s"""${R}Sub1$X != ${G}SubSub1$X + Sealed.Sub1(1), + Sealed.Sub2(1), + s"""${R}Sub1$X != ${G}Sub2${X} |${R}=== Obtained === |Sub1( | i: 1, |)$X |$G=== Expected === - |SubSub1( + |Sub2( | d: 1.0, |)$X""".stripMargin, ) @@ -693,7 +693,7 @@ class DifferSpec extends ScalaCheckSuite { test("Sealed trait: When only 'obtained' is provided when diffing") { assertConsoleDiffOutput( Differ[List[Sealed]], - List(Sub1(1)), + List(Sealed.Sub1(1)), List.empty[Sealed], s"""List( | ${R}Sub1( @@ -707,7 +707,7 @@ class DifferSpec extends ScalaCheckSuite { assertConsoleDiffOutput( Differ[List[Sealed]], List.empty[Sealed], - List(Sub1(1)), + List(Sealed.Sub1(1)), s"""List( | ${G}Sub1( | i: 1, @@ -737,13 +737,13 @@ class DifferSpec extends ScalaCheckSuite { .unsafeGet val diffResult = differ.diff( - SubSealed.SubSub2( + Sealed.Sub3( List( CC(1, "1", 1), CC(2, "2", 2), ), ), - SubSealed.SubSub2( + Sealed.Sub3( List( CC(2, "2", 2), CC(1, "1", 1), @@ -755,19 +755,19 @@ class DifferSpec extends ScalaCheckSuite { assertConsoleDiffOutput( differ, - SubSealed.SubSub2( + Sealed.Sub3( List( CC(1, "1", 1), CC(2, "2", 2), ), ), - SubSealed.SubSub2( + Sealed.Sub3( List( CC(2, "2", 2), CC(1, "2", 1), ), ), - s"""SubSub2( + s"""Sub3( | list: List( | CC( | i: 1, @@ -795,7 +795,7 @@ class DifferSpec extends ScalaCheckSuite { Left( ConfigureError.UnrecognizedSubType( ConfigurePath(Vector("nope"), List("list")), - Vector("Sub1", "SubSub1", "SubSub2", "Weird@Sub"), + Vector("Sub1", "Sub2", "Sub3", "Weird@Sub"), ), ), ) diff --git a/modules/coretest/src/test/scala/difflicious/testtypes.scala b/modules/coretest/src/test/scala/difflicious/testtypes.scala index 1cbfc82..2c83594 100644 --- a/modules/coretest/src/test/scala/difflicious/testtypes.scala +++ b/modules/coretest/src/test/scala/difflicious/testtypes.scala @@ -3,9 +3,8 @@ package difflicious import cats.kernel.Order import org.scalacheck.{Gen, Arbitrary} import difflicious.differ.{ValueDiffer, EqualsDiffer} -import difflicious.testtypes.SubSealed.{SubSub1, SubSub2} -object testtypes { +object testtypes extends ScalaVersionDependentTestTypes { // Dummy differ that fails when any of its method is called. For tests where we just need a Differ[T] def dummyDiffer[T]: Differ[T] = new Differ[T] { @@ -65,47 +64,42 @@ object testtypes { implicit val differ: ValueDiffer[NewInt] = Differ.intDiffer.contramap(_.int) } - sealed trait SealedWithCustom - - object SealedWithCustom { - case class Custom(i: Int) extends SealedWithCustom - object Custom { - implicit val differ: Differ[Custom] = Differ.derived[Custom].ignoreAt(_.i) - } - case class Normal(i: Int) extends SealedWithCustom - - implicit val differ: Differ[SealedWithCustom] = Differ.derived[SealedWithCustom] - } - sealed trait Sealed object Sealed { + case class Sub1(i: Int) extends Sealed + final case class Sub2(d: Double) extends Sealed + final case class Sub3(list: List[CC]) extends Sealed + final case class `Weird@Sub`(i: Int, `weird@Field`: String) extends Sealed + implicit val differ: Differ[Sealed] = Differ.derived[Sealed] - private val genSub1: Gen[Sub1] = Arbitrary.arbitrary[Int].map(Sub1) - private val genSubSub1: Gen[SubSub1] = Arbitrary.arbitrary[Double].map(SubSub1(_)) - private val genSubSub2: Gen[SubSub2] = Arbitrary.arbitrary[List[CC]].map(SubSub2) + private val genSub1: Gen[Sub1] = Arbitrary.arbitrary[Int].map(Sub1.apply) + private val genSub2: Gen[Sub2] = Arbitrary.arbitrary[Double].map(Sub2.apply) + private val genSub3: Gen[Sub3] = Arbitrary.arbitrary[List[CC]].map(Sub3.apply) implicit val arb: Arbitrary[Sealed] = Arbitrary( Gen.oneOf( genSub1, - genSubSub1, - genSubSub2, + genSub2, ), ) } - case class Sub1(i: Int) extends Sealed - final case class `Weird@Sub`(i: Int, `weird@Field`: String) extends Sealed + sealed trait SealedWithCustom - sealed trait SubSealed extends Sealed + object SealedWithCustom { + case class Custom(i: Int) extends SealedWithCustom + object Custom { + implicit val differ: Differ[Custom] = Differ.derived[Custom].ignoreAt(_.i) + } + case class Normal(i: Int) extends SealedWithCustom - object SubSealed { - case class SubSub1(d: Double) extends SubSealed - case class SubSub2(list: List[CC]) extends SubSealed + implicit val differ: Differ[SealedWithCustom] = Differ.derived[SealedWithCustom] } + final case class MapKey(a: Int, b: String) object MapKey { diff --git a/modules/coretest/src/test/scala/difflicious/testutils/package.scala b/modules/coretest/src/test/scala/difflicious/testutils/package.scala index dfc6d46..dd42c8b 100644 --- a/modules/coretest/src/test/scala/difflicious/testutils/package.scala +++ b/modules/coretest/src/test/scala/difflicious/testutils/package.scala @@ -17,7 +17,7 @@ package object testutils { val justIgnoredStr = s"[IGNORED]" def assertOkIfValuesEqualProp[A: Arbitrary](differ: Differ[A]): Prop = { - forAll { l: A => + forAll { (l: A) => val res = differ.diff(l, l) res.isOk } diff --git a/modules/scalatest/src/main/scala/difflicious/scalatest/ScalatestDiff.scala b/modules/scalatest/src/main/scala-2.13/difflicious/scalatest/ScalatestDiff.scala similarity index 100% rename from modules/scalatest/src/main/scala/difflicious/scalatest/ScalatestDiff.scala rename to modules/scalatest/src/main/scala-2.13/difflicious/scalatest/ScalatestDiff.scala diff --git a/modules/scalatest/src/main/scala-3/difflicious.scalatest/ScalatestDiff.scala b/modules/scalatest/src/main/scala-3/difflicious.scalatest/ScalatestDiff.scala new file mode 100644 index 0000000..289ae50 --- /dev/null +++ b/modules/scalatest/src/main/scala-3/difflicious.scalatest/ScalatestDiff.scala @@ -0,0 +1,16 @@ +package difflicious.scalatest + +import difflicious.{Differ, DiffResultPrinter} +import org.scalactic.source.Position +import org.scalatest.Assertions.fail + +trait ScalatestDiff { + extension [A](differ: Differ[A]) + inline def assertNoDiff(obtained: A, expected: A): Unit = { + val result = differ.diff(obtained, expected) + if (!result.isOk) + fail(DiffResultPrinter.consoleOutput(result, 0).render) + } +} + +object ScalatestDiff extends ScalatestDiff diff --git a/project/plugins.sbt b/project/plugins.sbt index 706d8cc..9104617 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -5,3 +5,4 @@ addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.8.2") addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.7") addSbtPlugin("com.47deg" % "sbt-microsites" % "1.3.4") addSbtPlugin("com.codecommit" % "sbt-github-actions" % "0.12.0") +addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.2.21") // override mdoc version from microsite From 48ed40952c9ca3dcf7aa11d265370c75401ff45f Mon Sep 17 00:00:00 2001 From: Jacob Wang Date: Tue, 27 Jul 2021 13:08:06 +0100 Subject: [PATCH 3/6] delme --- .scalafmt.conf | 3 +- build.sbt | 14 +-- .../internal/ConfigureMethods.scala | 88 ++++++++++++++++++- 3 files changed, 93 insertions(+), 12 deletions(-) diff --git a/.scalafmt.conf b/.scalafmt.conf index 1038f9f..5b63f1e 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,7 +1,8 @@ -version=2.3.2 +version="3.0.0-RC3" maxColumn = 120 trailingCommas = always continuationIndent.defnSite = 2 rewrite.rules = [PreferCurlyFors] rewrite.redundantBraces.stringInterpolation = true +runner.dialect = scala3 diff --git a/build.sbt b/build.sbt index e6a0b88..a3a4cba 100644 --- a/build.sbt +++ b/build.sbt @@ -38,9 +38,9 @@ lazy val core = Project("difflicious-core", file("modules/core")) libraryDependencies ++= Seq( if (isScala3.value) "com.softwaremill.magnolia1_3" %% "magnolia" % "1.0.0-M4" // if (isScala3.value) "com.softwaremill.magnolia" %% "magnolia-core" % "2.0.0-M7-SNAPSHOT" - else "com.softwaremill.magnolia" %% "magnolia-core" % "1.0.0-M4", - "dev.zio" %% "izumi-reflect" % "1.1.2", - "com.lihaoyi" %% "fansi" % "0.2.14", + else "com.softwaremill.magnolia" %% "magnolia-core" % "1.0.0-M4", + "dev.zio" %% "izumi-reflect" % "1.1.2", + "com.lihaoyi" %% "fansi" % "0.2.14", ) ++ ( if (scalaVersion.value.startsWith("2")) Seq("org.scala-lang" % "scala-reflect" % "2.13.5") @@ -93,7 +93,7 @@ lazy val coretest = Project("coretest", file("modules/coretest")) ), // Test deps libraryDependencies ++= Seq( - "org.scalameta" %% "munit" % munitVersion, + "org.scalameta" %% "munit" % munitVersion, "org.scalameta" %% "munit-scalacheck" % munitVersion, ).map(_ % Test), ) @@ -108,7 +108,7 @@ lazy val docs = project .settings( libraryDependencies ++= Seq( "org.scalatest" %% "scalatest" % scalatestVersion, - "org.scalameta" %% "mdoc" % "2.2.21", + "org.scalameta" %% "mdoc" % "2.2.21", ), ) .settings( @@ -160,8 +160,8 @@ lazy val commonSettings = Seq( versionScheme := Some("early-semver"), scalacOptions ++= (if (isScala3.value) Seq.empty[String] else Seq("-Wmacros:after")), libraryDependencies ++= Seq( - compilerPlugin("org.typelevel" %% "kind-projector" % "0.13.0" cross CrossVersion.full), - compilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1"), + compilerPlugin("org.typelevel" %% "kind-projector" % "0.13.0" cross CrossVersion.full), + compilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1"), ).filterNot(_ => isScala3.value), ) diff --git a/modules/core/src/main/scala-3/difflicious/internal/ConfigureMethods.scala b/modules/core/src/main/scala-3/difflicious/internal/ConfigureMethods.scala index f7feb6c..524f394 100644 --- a/modules/core/src/main/scala-3/difflicious/internal/ConfigureMethods.scala +++ b/modules/core/src/main/scala-3/difflicious/internal/ConfigureMethods.scala @@ -1,13 +1,93 @@ package difflicious.internal -import difflicious.Differ +import difflicious.{ConfigurePath, Differ, ConfigureOp} + import scala.annotation.nowarn +import scala.quoted.* +import scala.annotation.tailrec +import difflicious.internal.ConfigureMethodImpls._ +import difflicious.internal.EitherGetSyntax._ trait ConfigureMethods[T]: this: Differ[T] => - inline def ignoreAt[U](path: T => U): Differ[T] = ??? // FIXME: + val requiredShapeMsg = "Configure path must have shape like: _.field1.each.field2.subType[ASubClass]" + + inline def ignoreAt[U](inline path: T => U): Differ[T] = + ${ ignoreAt_impl('this, 'path) } + + inline def configure[U](path: T => U)(configFunc: Differ[U] => Differ[U]): Differ[T] = + ${ configure_impl('this, 'path, 'configFunc) } + + inline def replace[U](path: T => U)(newDiffer: Differ[U]): Differ[T] = + ${ replace_impl('this, 'path, 'newDiffer) } + +private[difflicious] object ConfigureMethodImpls: + + def ignoreAt_impl[T: Type, U](differ: Expr[Differ[T]], path: Expr[T => U])(using Quotes): Expr[Differ[T]] = { + '{ + ${ differ } + .configureRaw(ConfigurePath.fromPath(${ collectPathElements(path) }), ConfigureOp.ignore) + .unsafeGet + } + } + + def configure_impl[T: Type, U: Type]( + differ: Expr[Differ[T]], + path: Expr[T => U], + configFunc: Expr[Differ[U] => Differ[U]], + )(using + Quotes, + ): Expr[Differ[T]] = + '{ + ${ differ } + .configureRaw( + ConfigurePath.fromPath(${ collectPathElements(path) }), + ConfigureOp.TransformDiffer(${ configFunc }), + ) + .unsafeGet + } + + def replace_impl[T: Type, U: Type]( + differ: Expr[Differ[T]], + path: Expr[T => U], + newDiffer: Expr[Differ[U]], + )(using + Quotes, + ): Expr[Differ[T]] = + '{ + ${ differ } + .configureRaw( + ConfigurePath.fromPath(${ collectPathElements(path) }), + ConfigureOp.TransformDiffer[U](_ => ${ newDiffer }), + ) + .unsafeGet + } - inline def configure[U](path: T => U)(configFunc: Differ[U] => Differ[U]): Differ[T] = ??? // FIXME: + def collectPathElements[T, U](pathExpr: Expr[T => U])(using Quotes): Expr[List[String]] = { + import quotes.reflect.* - inline def replace[U](path: T => U)(newDiffer: Differ[U]): Differ[T] = ??? // FIXME: + @tailrec + def goCollect(pathAccum: List[String], cur: Term): List[String] = + // FIXME: + cur match { + case Select(rest, name) => + goCollect(name.toString :: pathAccum, rest) + case TypeApply(Select(Apply(_, rest :: Nil), "subType"), TypeSelect(_, subTypeName) :: Nil) => + // FIXME: need to check is sealed trait subtype + goCollect(subTypeName.toString :: pathAccum, rest) + case Apply(Apply(TypeApply(Ident(name), _), rest :: Nil), _) if name.toString == "toEachableOps" => + goCollect(pathAccum, rest) + case Ident(_) => pathAccum + case _ => { + throw new Exception("FIXME handle other cases than Select") + } + } + pathExpr.asTerm match { + case Inlined(_, _, Block(List(DefDef(_, _, _, Some(Select(rest, name)))), _)) => + Expr(goCollect(List(name.toString), rest)) + case _ => + println(pathExpr.asTerm.show(using Printer.TreeStructure)) + Expr(List(pathExpr.asTerm.show(using Printer.TreeStructure))) + } + } From 0c92557a12bb9275011a2df9c46e51f3517232e0 Mon Sep 17 00:00:00 2001 From: Jacob Wang Date: Thu, 5 Aug 2021 00:02:35 +0100 Subject: [PATCH 4/6] tests passes --- .github/workflows/ci.yml | 12 +++- build.sbt | 8 +-- .../difflicious/utils/TypeNamePlatform.scala | 9 --- .../internal/ConfigureMethods.scala | 65 ++++++++++++------- .../difflicious/utils/TypeNamePlatform.scala | 5 -- .../scala/difflicious/utils/TypeName.scala | 6 +- .../ScalaVersionDependentTestTypes.scala | 7 +- .../ScalaVersionDependentTests.scala | 10 ++- .../ScalaVersionDependentTests.scala | 15 ++++- .../difflicious/DifferConfigureSpec.scala | 6 -- .../test/scala/difflicious/DifferSpec.scala | 2 +- .../test/scala/difflicious/testtypes.scala | 7 +- 12 files changed, 89 insertions(+), 63 deletions(-) delete mode 100644 modules/core/src/main/scala-2.13/difflicious/utils/TypeNamePlatform.scala delete mode 100644 modules/core/src/main/scala-3/difflicious/utils/TypeNamePlatform.scala diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9038707..1a138f3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - scala: [2.13.6] + scala: [2.13.6, 3.0.1] java: [adopt@1.11] runs-on: ${{ matrix.os }} steps: @@ -115,6 +115,16 @@ jobs: tar xf targets.tar rm targets.tar + - name: Download target directories (3.0.1) + uses: actions/download-artifact@v2 + with: + name: target-${{ matrix.os }}-3.0.1-${{ matrix.java }} + + - name: Inflate target directories (3.0.1) + run: | + tar xf targets.tar + rm targets.tar + - name: Setup ruby uses: actions/setup-ruby@v1 with: diff --git a/build.sbt b/build.sbt index a3a4cba..63ac481 100644 --- a/build.sbt +++ b/build.sbt @@ -12,7 +12,7 @@ val isScala3 = Def.setting { inThisBuild( List( - scalaVersion := scala3, // FIXME: + scalaVersion := scala213, crossScalaVersions := Seq(scala213, scala3), organization := "com.github.jatcwang", homepage := Some(url("https://github.com/jatcwang/difflicious")), @@ -42,10 +42,10 @@ lazy val core = Project("difflicious-core", file("modules/core")) "dev.zio" %% "izumi-reflect" % "1.1.2", "com.lihaoyi" %% "fansi" % "0.2.14", ) ++ ( - if (scalaVersion.value.startsWith("2")) - Seq("org.scala-lang" % "scala-reflect" % "2.13.5") - else + if (isScala3.value) Seq.empty + else + Seq("org.scala-lang" % "scala-reflect" % scala213) ), Compile / sourceGenerators += Def.task { val file = (Compile / sourceManaged).value / "difflicious" / "TupleDifferInstances.scala" diff --git a/modules/core/src/main/scala-2.13/difflicious/utils/TypeNamePlatform.scala b/modules/core/src/main/scala-2.13/difflicious/utils/TypeNamePlatform.scala deleted file mode 100644 index f3dde69..0000000 --- a/modules/core/src/main/scala-2.13/difflicious/utils/TypeNamePlatform.scala +++ /dev/null @@ -1,9 +0,0 @@ -package difflicious.utils - -trait TypeNamePlatform { - - def unescapeIdentifierName(str: String): String = { - scala.reflect.runtime.universe.TypeName(str).decodedName.toString - } - -} diff --git a/modules/core/src/main/scala-3/difflicious/internal/ConfigureMethods.scala b/modules/core/src/main/scala-3/difflicious/internal/ConfigureMethods.scala index 524f394..47f474b 100644 --- a/modules/core/src/main/scala-3/difflicious/internal/ConfigureMethods.scala +++ b/modules/core/src/main/scala-3/difflicious/internal/ConfigureMethods.scala @@ -4,8 +4,7 @@ import difflicious.{ConfigurePath, Differ, ConfigureOp} import scala.annotation.nowarn import scala.quoted.* import scala.annotation.tailrec -import difflicious.internal.ConfigureMethodImpls._ -import difflicious.internal.EitherGetSyntax._ +import difflicious.internal.ConfigureMethodImpls.* trait ConfigureMethods[T]: this: Differ[T] => @@ -15,19 +14,21 @@ trait ConfigureMethods[T]: inline def ignoreAt[U](inline path: T => U): Differ[T] = ${ ignoreAt_impl('this, 'path) } - inline def configure[U](path: T => U)(configFunc: Differ[U] => Differ[U]): Differ[T] = + inline def configure[U](inline path: T => U)(configFunc: Differ[U] => Differ[U]): Differ[T] = ${ configure_impl('this, 'path, 'configFunc) } - inline def replace[U](path: T => U)(newDiffer: Differ[U]): Differ[T] = + inline def replace[U](inline path: T => U)(newDiffer: Differ[U]): Differ[T] = ${ replace_impl('this, 'path, 'newDiffer) } private[difflicious] object ConfigureMethodImpls: def ignoreAt_impl[T: Type, U](differ: Expr[Differ[T]], path: Expr[T => U])(using Quotes): Expr[Differ[T]] = { '{ - ${ differ } - .configureRaw(ConfigurePath.fromPath(${ collectPathElements(path) }), ConfigureOp.ignore) - .unsafeGet + (${ differ } + .configureRaw(ConfigurePath.fromPath(${ collectPathElements(path) }), ConfigureOp.ignore)) match { + case Right(d) => d + case Left(e) => throw e + } } } @@ -39,12 +40,14 @@ private[difflicious] object ConfigureMethodImpls: Quotes, ): Expr[Differ[T]] = '{ - ${ differ } + (${ differ } .configureRaw( ConfigurePath.fromPath(${ collectPathElements(path) }), ConfigureOp.TransformDiffer(${ configFunc }), - ) - .unsafeGet + )) match { + case Right(d) => d + case Left(e) => throw e + } } def replace_impl[T: Type, U: Type]( @@ -59,35 +62,49 @@ private[difflicious] object ConfigureMethodImpls: .configureRaw( ConfigurePath.fromPath(${ collectPathElements(path) }), ConfigureOp.TransformDiffer[U](_ => ${ newDiffer }), - ) - .unsafeGet + ) match { + case Right(d) => d + case Left(e) => throw e + } } def collectPathElements[T, U](pathExpr: Expr[T => U])(using Quotes): Expr[List[String]] = { import quotes.reflect.* +// import dotty.tools.dotc.ast.Trees._ + def resolveSubTypeName(typeTree: Tree)(using Quotes): String = + typeTree match { + case TypeIdent(name) => name.toString + case TypeSelect(_, name) => name.toString + } + @tailrec - def goCollect(pathAccum: List[String], cur: Term): List[String] = - // FIXME: + def collectPathElementsLoop(pathAccum: List[String], cur: Term): List[String] = cur match { case Select(rest, name) => - goCollect(name.toString :: pathAccum, rest) - case TypeApply(Select(Apply(_, rest :: Nil), "subType"), TypeSelect(_, subTypeName) :: Nil) => - // FIXME: need to check is sealed trait subtype - goCollect(subTypeName.toString :: pathAccum, rest) + collectPathElementsLoop(name.toString :: pathAccum, rest) + case x @ TypeApply(Select(Apply(TypeApply(_, superType :: Nil), rest :: Nil), "subType"), subType :: Nil) => + if superType.symbol.children.contains(subType.symbol) then + collectPathElementsLoop(resolveSubTypeName(subType) :: pathAccum, rest) + else + report.error( + s"subType requires that the super type be a sealed trait (enum), and the subtype being a direct children of the super type.", + x.asExpr, + ) + List.empty case Apply(Apply(TypeApply(Ident(name), _), rest :: Nil), _) if name.toString == "toEachableOps" => - goCollect(pathAccum, rest) + collectPathElementsLoop(pathAccum, rest) case Ident(_) => pathAccum case _ => { - throw new Exception("FIXME handle other cases than Select") + throw new Exception(cur.show(using Printer.TreeShortCode) ++ "|||" ++ cur.show(using Printer.TreeStructure)) } } pathExpr.asTerm match { - case Inlined(_, _, Block(List(DefDef(_, _, _, Some(Select(rest, name)))), _)) => - Expr(goCollect(List(name.toString), rest)) + case Inlined(_, _, Block(List(DefDef(_, _, _, Some(tree))), _)) => + Expr(collectPathElementsLoop(List.empty, tree)) case _ => - println(pathExpr.asTerm.show(using Printer.TreeStructure)) - Expr(List(pathExpr.asTerm.show(using Printer.TreeStructure))) + report.error(s"XXX ${pathExpr.asTerm.show(using Printer.TreeStructure)}") + '{ ??? } } } diff --git a/modules/core/src/main/scala-3/difflicious/utils/TypeNamePlatform.scala b/modules/core/src/main/scala-3/difflicious/utils/TypeNamePlatform.scala deleted file mode 100644 index faf2b63..0000000 --- a/modules/core/src/main/scala-3/difflicious/utils/TypeNamePlatform.scala +++ /dev/null @@ -1,5 +0,0 @@ -package difflicious.utils - -trait TypeNamePlatform { - def unescapeIdentifierName(str: String): String = ??? -} diff --git a/modules/core/src/main/scala/difflicious/utils/TypeName.scala b/modules/core/src/main/scala/difflicious/utils/TypeName.scala index 6d2697a..d3fecde 100644 --- a/modules/core/src/main/scala/difflicious/utils/TypeName.scala +++ b/modules/core/src/main/scala/difflicious/utils/TypeName.scala @@ -8,7 +8,7 @@ final case class TypeName[A](long: String, short: String, typeArguments: List[Ty } } -object TypeName extends TypeNamePlatform { +object TypeName { // A type name without a compile time type known. type SomeTypeName = TypeName[_] @@ -19,8 +19,8 @@ object TypeName extends TypeNamePlatform { def fromLightTypeTag[A](t: LightTypeTag): TypeName[A] = { TypeName[A]( - long = unescapeIdentifierName(t.longName), - short = unescapeIdentifierName(t.shortName), + long = t.longName, + short = t.shortName, typeArguments = t.typeArgs.map(fromLightTypeTag), ) } diff --git a/modules/coretest/src/test/scala-2.13/difflicious/ScalaVersionDependentTestTypes.scala b/modules/coretest/src/test/scala-2.13/difflicious/ScalaVersionDependentTestTypes.scala index 0b3bb54..daab98f 100644 --- a/modules/coretest/src/test/scala-2.13/difflicious/ScalaVersionDependentTestTypes.scala +++ b/modules/coretest/src/test/scala-2.13/difflicious/ScalaVersionDependentTestTypes.scala @@ -1,9 +1,8 @@ package difflicious import difflicious.Differ -import difflicious.testtypes.SubSealed.{SubSub1, SubSub2} import org.scalacheck.{Arbitrary, Gen} -import difflicious.testtypes.{Sub1, CC} +import difflicious.implicits._ trait ScalaVersionDependentTestTypes { sealed trait SealedNested @@ -19,11 +18,11 @@ trait ScalaVersionDependentTestTypes { import SubSealed._ implicit val arb: Arbitrary[SealedNested] = { - val sub1 = Gen.posNum[Int].map(SubFoo.apply) + val subFoo = Gen.posNum[Int].map(SubFoo.apply) val subsub1 = Gen.posNum[Double].map(SubSub1.apply) val subsub2 = Gen.alphaStr.map(SubSub2.apply) - Arbitrary(Gen.oneOf(sub1, subsub1, subsub2)) + Arbitrary(Gen.oneOf(subFoo, subsub1, subsub2)) } implicit val differ: Differ[SealedNested] = Differ.derived[SealedNested] } diff --git a/modules/coretest/src/test/scala-2.13/difflicious/ScalaVersionDependentTests.scala b/modules/coretest/src/test/scala-2.13/difflicious/ScalaVersionDependentTests.scala index 322efd0..8d329e8 100644 --- a/modules/coretest/src/test/scala-2.13/difflicious/ScalaVersionDependentTests.scala +++ b/modules/coretest/src/test/scala-2.13/difflicious/ScalaVersionDependentTests.scala @@ -1,13 +1,15 @@ package difflicious import difflicious.testtypes._ +import difflicious.testtypes.SealedNested.SubSealed import difflicious.testutils._ +import difflicious.implicits._ import munit.FunSuite trait ScalaVersionDependentTests { this: FunSuite => test("configure path's subType handles multi-level hierarchies") { assertConsoleDiffOutput( - Differ[List[Sealed]].ignoreAt(_.each.subType[SubSealed.SubSub1].d), + Differ[List[SealedNested]].ignoreAt(_.each.subType[SubSealed.SubSub1].d), List( SubSealed.SubSub1(1.0), ), @@ -22,4 +24,10 @@ trait ScalaVersionDependentTests { this: FunSuite => ) } + test("configure path's subType call errors when super type isn't sealed") { + val compileError = compileErrors("Differ[List[OpenSuperType]].ignoreAt(_.each.subType[OpenSub])") + val firstLine = compileError.linesIterator.toList.drop(1).head + assertEquals(firstLine, "Specified subtype is not a known direct subtype of trait OpenSuperType.") + } + } diff --git a/modules/coretest/src/test/scala-3/difflicious/ScalaVersionDependentTests.scala b/modules/coretest/src/test/scala-3/difflicious/ScalaVersionDependentTests.scala index 3179aa8..7233281 100644 --- a/modules/coretest/src/test/scala-3/difflicious/ScalaVersionDependentTests.scala +++ b/modules/coretest/src/test/scala-3/difflicious/ScalaVersionDependentTests.scala @@ -1,5 +1,18 @@ package difflicious -trait ScalaVersionDependentTests {// self: munit.FunSuite => +import difflicious.testtypes._ +import difflicious.implicits._ + +trait ScalaVersionDependentTests { this: munit.FunSuite => + + test("configure path's subType call errors when super type isn't sealed") { + val compileError = compileErrors("Differ[List[OpenSuperType]].ignoreAt(_.each.subType[OpenSub])") + val firstLine = compileError.linesIterator.toList.head + println(compileError) + assertEquals( + firstLine, + "error: subType requires that the super type be a sealed trait (enum), and the subtype being a direct children of the super type.", + ) + } } diff --git a/modules/coretest/src/test/scala/difflicious/DifferConfigureSpec.scala b/modules/coretest/src/test/scala/difflicious/DifferConfigureSpec.scala index 1f6ff21..1528e26 100644 --- a/modules/coretest/src/test/scala/difflicious/DifferConfigureSpec.scala +++ b/modules/coretest/src/test/scala/difflicious/DifferConfigureSpec.scala @@ -30,12 +30,6 @@ class DifferConfigureSpec extends munit.FunSuite { ) } - test("configure path's subType call errors when super type isn't sealed") { - val compileError = compileErrors("Differ[List[OpenSuperType]].ignoreAt(_.each.subType[OpenSub])") - val firstLine = compileError.linesIterator.toList.drop(1).head - assertEquals(firstLine, "Specified subtype is not a known direct subtype of trait OpenSuperType.") - } - test("configure path allows 'each' to resolve underlying differ in a Map") { assertConsoleDiffOutput( Differ[Map[String, CC]].ignoreAt(_.each.dd), diff --git a/modules/coretest/src/test/scala/difflicious/DifferSpec.scala b/modules/coretest/src/test/scala/difflicious/DifferSpec.scala index 5cd5dec..3921fa2 100644 --- a/modules/coretest/src/test/scala/difflicious/DifferSpec.scala +++ b/modules/coretest/src/test/scala/difflicious/DifferSpec.scala @@ -731,7 +731,7 @@ class DifferSpec extends ScalaCheckSuite with ScalaVersionDependentTests { val differ = Differ[Sealed] .configureRaw( ConfigurePath - .of("SubSub2", "list"), + .of("Sub3", "list"), ConfigureOp.PairBy.ByFunc[CC, Int](_.i), ) .unsafeGet diff --git a/modules/coretest/src/test/scala/difflicious/testtypes.scala b/modules/coretest/src/test/scala/difflicious/testtypes.scala index 2c83594..65a8996 100644 --- a/modules/coretest/src/test/scala/difflicious/testtypes.scala +++ b/modules/coretest/src/test/scala/difflicious/testtypes.scala @@ -27,8 +27,8 @@ object testtypes extends ScalaVersionDependentTestTypes { case class HasASeq[A](seq: Seq[A]) object HasASeq { - implicit def differ[A]( - implicit differ: Differ[Seq[A]], + implicit def differ[A](implicit + differ: Differ[Seq[A]], ): Differ[HasASeq[A]] = { Differ.derived[HasASeq[A]] } @@ -82,11 +82,11 @@ object testtypes extends ScalaVersionDependentTestTypes { Gen.oneOf( genSub1, genSub2, + genSub3, ), ) } - sealed trait SealedWithCustom object SealedWithCustom { @@ -99,7 +99,6 @@ object testtypes extends ScalaVersionDependentTestTypes { implicit val differ: Differ[SealedWithCustom] = Differ.derived[SealedWithCustom] } - final case class MapKey(a: Int, b: String) object MapKey { From a6705cd396122b767dcce7c1517d0f45c069c5b1 Mon Sep 17 00:00:00 2001 From: Jacob Wang Date: Sat, 14 Aug 2021 23:41:32 +0100 Subject: [PATCH 5/6] add test to enum derived instances --- build.sbt | 4 ++-- .../ScalaVersionDependentTestTypes.scala | 1 - .../ScalaVersionDependentTestTypes.scala | 17 ++++++++++++++- .../ScalaVersionDependentTests.scala | 21 ++++++++++++++----- 4 files changed, 34 insertions(+), 9 deletions(-) diff --git a/build.sbt b/build.sbt index 63ac481..2a00c4d 100644 --- a/build.sbt +++ b/build.sbt @@ -127,7 +127,7 @@ lazy val docs = project micrositeGithubToken := sys.env.get("GITHUB_TOKEN"), ) .settings( - // Disble any2stringAdd deprecation in md files. Seems like mdoc macro generates code which + // Disable any2stringAdd deprecation in md files. Seems like mdoc macro generates code which // use implicit conversion to string scalacOptions ~= { opts => val extraOpts = @@ -151,7 +151,7 @@ lazy val benchmarks = Project("benchmarks", file("modules/benchmarks")) lazy val commonSettings = Seq( scalacOptions --= { - if (sys.env.get("CI").isDefined) { + if (sys.env.get("CI").isDefined && !isScala3.value) { // TODO: Reenable Scala 3 fatal warnings once nowarn is supported Seq.empty } else { Seq("-Xfatal-warnings") diff --git a/modules/coretest/src/test/scala-2.13/difflicious/ScalaVersionDependentTestTypes.scala b/modules/coretest/src/test/scala-2.13/difflicious/ScalaVersionDependentTestTypes.scala index daab98f..82222a1 100644 --- a/modules/coretest/src/test/scala-2.13/difflicious/ScalaVersionDependentTestTypes.scala +++ b/modules/coretest/src/test/scala-2.13/difflicious/ScalaVersionDependentTestTypes.scala @@ -2,7 +2,6 @@ package difflicious import difflicious.Differ import org.scalacheck.{Arbitrary, Gen} -import difflicious.implicits._ trait ScalaVersionDependentTestTypes { sealed trait SealedNested diff --git a/modules/coretest/src/test/scala-3/difflicious/ScalaVersionDependentTestTypes.scala b/modules/coretest/src/test/scala-3/difflicious/ScalaVersionDependentTestTypes.scala index f94ea3e..5c2d555 100644 --- a/modules/coretest/src/test/scala-3/difflicious/ScalaVersionDependentTestTypes.scala +++ b/modules/coretest/src/test/scala-3/difflicious/ScalaVersionDependentTestTypes.scala @@ -1,4 +1,19 @@ package difflicious +import org.scalacheck.{Arbitrary, Gen} + trait ScalaVersionDependentTestTypes: - val x=1 + enum MyEnum { + case I + case V(i: Int) + } + + object MyEnum: + given Differ[MyEnum] = Differ.derived[MyEnum] + + given Arbitrary[MyEnum] = Arbitrary( + Gen.oneOf( + Gen.const(MyEnum.I), + Gen.posNum[Int].map(MyEnum.V.apply), + ), + ) diff --git a/modules/coretest/src/test/scala-3/difflicious/ScalaVersionDependentTests.scala b/modules/coretest/src/test/scala-3/difflicious/ScalaVersionDependentTests.scala index 7233281..c7e56cf 100644 --- a/modules/coretest/src/test/scala-3/difflicious/ScalaVersionDependentTests.scala +++ b/modules/coretest/src/test/scala-3/difflicious/ScalaVersionDependentTests.scala @@ -1,18 +1,29 @@ package difflicious -import difflicious.testtypes._ -import difflicious.implicits._ +import difflicious.testtypes.* +import difflicious.implicits.* +import difflicious.testutils.* -trait ScalaVersionDependentTests { this: munit.FunSuite => +trait ScalaVersionDependentTests: + this: munit.FunSuite => test("configure path's subType call errors when super type isn't sealed") { val compileError = compileErrors("Differ[List[OpenSuperType]].ignoreAt(_.each.subType[OpenSub])") val firstLine = compileError.linesIterator.toList.head - println(compileError) assertEquals( firstLine, "error: subType requires that the super type be a sealed trait (enum), and the subtype being a direct children of the super type.", ) } -} + test("Derived Enum: isOk == true if two values are equal") { + assertOkIfValuesEqualProp(MyEnum.given_Differ_MyEnum) + } + + test("Derived Enum: isOk == false if two values are NOT equal") { + assertNotOkIfNotEqualProp(MyEnum.given_Differ_MyEnum) + } + + test("Derived Enum: isOk always true if differ is marked ignored") { + assertIsOkIfIgnoredProp(MyEnum.given_Differ_MyEnum) + } From 367695dfbe55ebe002220c0090b5e5183ce6b0fc Mon Sep 17 00:00:00 2001 From: Jacob Wang Date: Sun, 22 Aug 2021 13:47:39 +0100 Subject: [PATCH 6/6] docs module only for 2.13 --- build.sbt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 2a00c4d..29d4770 100644 --- a/build.sbt +++ b/build.sbt @@ -98,7 +98,7 @@ lazy val coretest = Project("coretest", file("modules/coretest")) ).map(_ % Test), ) -lazy val docs = project +lazy val docs: Project = project .dependsOn(core, coretest, cats, munit, scalatest) .enablePlugins(MicrositesPlugin) .settings( @@ -110,6 +110,11 @@ lazy val docs = project "org.scalatest" %% "scalatest" % scalatestVersion, "org.scalameta" %% "mdoc" % "2.2.21", ), + makeMicrosite := Def.taskDyn { + val orig = (ThisProject / makeMicrosite).taskValue + if (isScala3.value) Def.task({}) + else Def.task(orig.value) + }.value ) .settings( mdocIn := file("docs/docs"),