diff --git a/modules/core/shared/src/main/scala/neotype/package.scala b/modules/core/shared/src/main/scala/neotype/package.scala index da77274..c40d797 100644 --- a/modules/core/shared/src/main/scala/neotype/package.scala +++ b/modules/core/shared/src/main/scala/neotype/package.scala @@ -143,6 +143,8 @@ abstract class Newtype[A] extends TypeWrapper[A]: object Newtype: type WithType[A, B] = Newtype[A] { type Type = B } + given canEqual[A, B](using Newtype.WithType[A, B], CanEqual[A, A]): CanEqual[B, B] = CanEqual.derived + extension [A, B](value: B)(using newtype: Newtype.WithType[A, B] ) // diff --git a/modules/core/shared/src/test/scala/neotype/NewtypeSpec.scala b/modules/core/shared/src/test/scala/neotype/NewtypeSpec.scala index 25367a0..7dbe2e7 100644 --- a/modules/core/shared/src/test/scala/neotype/NewtypeSpec.scala +++ b/modules/core/shared/src/test/scala/neotype/NewtypeSpec.scala @@ -253,6 +253,13 @@ object NewtypeSpec extends ZIOSpecDefault: assertTrue(res.message contains "Neotype Error") } + test("comparison between types that don't have a CanEqual should be possible when strictEquality is not enabled"){ + val x: Right[Nothing, NoCanEqualNewtype] = Right(NoCanEqualNewtype(NoCanEqual(10))) + val y: Either[Throwable, NoCanEqualSubtype] = Right(NoCanEqualSubtype(NoCanEqual(10))) + + assertTrue(x == y) + } + suiteAll("makeOrThrow") { test("newtype success") { val res = PositiveIntNewtype.makeOrThrow(1) @@ -285,4 +292,41 @@ object NewtypeSpec extends ZIOSpecDefault: assertTrue(error.getMessage == "Must be the secret string!") } } + + suiteAll("CanEqual") { + import scala.language.strictEquality + + test("newtypes should be able to be compared to their own types")( + assertTrue(PositiveIntNewtype(10) == PositiveIntNewtype(10)) + ) + + test("newtypes should not be able to be compared to their underlying types") { + + val res = typeCheckErrors("""PositiveIntNewtype(10) == 10""").head + + assertTrue(res.message contains "Values of types neotype.PositiveIntNewtype.Type and Int cannot be compared with == or !=.") + } + + test("newtypes should not be able to be if their underlying type is not comparable") { + + val res = typeCheckErrors("""NoCanEqualNewtype(NoCanEqual(10)) == NoCanEqualNewtype(NoCanEqual(10))""").head + + assertTrue(res.message contains "Values of types neotype.NoCanEqualNewtype.Type and neotype.NoCanEqualNewtype.Type cannot be compared with == or !=.") + } + + test("subtypes should be able to be compared to their own types")( + assertTrue(PositiveIntSubtype(10) == PositiveIntSubtype(10)) + ) + + test("subtypes should be able to be compared to their underlying types") ( + assertTrue(PositiveIntSubtype(10) == 10) + ) + + test("subtypes should not be able to be if their underlying type is not comparable") { + + val res = typeCheckErrors("""NoCanEqualSubtype(NoCanEqual(10)) == NoCanEqual(10)""").head + + assertTrue(res.message contains "Values of types neotype.NoCanEqualSubtype.Type and neotype.NoCanEqual cannot be compared with == or !=") + } + } } diff --git a/modules/core/shared/src/test/scala/neotype/TestNewtypes.scala b/modules/core/shared/src/test/scala/neotype/TestNewtypes.scala index c28a2d4..8efd815 100644 --- a/modules/core/shared/src/test/scala/neotype/TestNewtypes.scala +++ b/modules/core/shared/src/test/scala/neotype/TestNewtypes.scala @@ -118,3 +118,11 @@ type NonNegativeCents = NonNegativeCents.Type object NonNegativeCents extends Subtype[Cents]: override inline def validate(input: Cents): Boolean = input.unwrap >= 0 + +case class NoCanEqual(value: Int) + +type NoCanEqualNewtype = NoCanEqualNewtype.Type +object NoCanEqualNewtype extends Newtype[NoCanEqual] + +type NoCanEqualSubtype = NoCanEqualSubtype.Type +object NoCanEqualSubtype extends Subtype[NoCanEqual] diff --git a/modules/neotype-caliban/shared/src/test/scala/neotype/interop/caliban/CalibanSpec.scala b/modules/neotype-caliban/shared/src/test/scala/neotype/interop/caliban/CalibanSpec.scala index c5d3b42..ae01f64 100644 --- a/modules/neotype-caliban/shared/src/test/scala/neotype/interop/caliban/CalibanSpec.scala +++ b/modules/neotype-caliban/shared/src/test/scala/neotype/interop/caliban/CalibanSpec.scala @@ -57,7 +57,7 @@ object CalibanSpec extends ZIOSpecDefault: }, test("ArgBuilder success") { val argBuilder = summonInline[ArgBuilder[SimpleSubtype]] - assertTrue(argBuilder.build(IntValue(123)) == Right(SimpleNewtype(123))) + assertTrue(argBuilder.build(IntValue(123)) == Right(SimpleSubtype(123))) } ) ) diff --git a/modules/neotype-chimney/shared/src/test/scala/neotype/interop/chimney/ChimneySpec.scala b/modules/neotype-chimney/shared/src/test/scala/neotype/interop/chimney/ChimneySpec.scala index 8787c42..d21fba3 100644 --- a/modules/neotype-chimney/shared/src/test/scala/neotype/interop/chimney/ChimneySpec.scala +++ b/modules/neotype-chimney/shared/src/test/scala/neotype/interop/chimney/ChimneySpec.scala @@ -33,7 +33,7 @@ object ChimneySpec extends ZIOSpecDefault: test("partially transform success") { val string = "hello world" val result = string.transformIntoPartial[ValidatedSubtype] - assertTrue(result.asEither == Right(ValidatedNewtype("hello world"))) + assertTrue(result.asEither == Right(ValidatedSubtype("hello world"))) }, test("partially transform failure") { val string = "hello" diff --git a/modules/neotype-doobie/shared/src/main/scala/neotype/interop/doobie/DoobieInstances.scala b/modules/neotype-doobie/shared/src/main/scala/neotype/interop/doobie/DoobieInstances.scala index 9a47b12..b39f390 100644 --- a/modules/neotype-doobie/shared/src/main/scala/neotype/interop/doobie/DoobieInstances.scala +++ b/modules/neotype-doobie/shared/src/main/scala/neotype/interop/doobie/DoobieInstances.scala @@ -17,7 +17,7 @@ given put[A, B](using nt: WrappedType[A, B], put: Put[A]): Put[B] = nt.unsafeMakeF(put) given read[A, B](using wrappedType: WrappedType[A, B], read: Read[A]): Read[B] = - read.map(value => wrappedType.makeOrThrow(value)) + read.map(value => wrappedType.makeOrThrow(value)) given write[A, B](using wrappedType: WrappedType[A, B], write: Write[A]): Write[B] = write.contramap(wrappedType.unwrap) diff --git a/modules/neotype-doobie/shared/src/test/scala/neotype/interop/doobie/DoobieInstancesSpec.scala b/modules/neotype-doobie/shared/src/test/scala/neotype/interop/doobie/DoobieInstancesSpec.scala index 97e2a1a..5ee1593 100644 --- a/modules/neotype-doobie/shared/src/test/scala/neotype/interop/doobie/DoobieInstancesSpec.scala +++ b/modules/neotype-doobie/shared/src/test/scala/neotype/interop/doobie/DoobieInstancesSpec.scala @@ -7,7 +7,8 @@ import _root_.doobie.util.transactor.Transactor import cats.Show import cats.effect.IO import cats.effect.unsafe.implicits.global -import neotype.{Newtype, Subtype} +import neotype.Newtype +import neotype.Subtype import neotype.common.NonEmptyString import neotype.interop.doobie.given import neotype.test.definitions.*