Skip to content

Commit

Permalink
Merge pull request #2070 from tpolecat/tuple_read_write_instances
Browse files Browse the repository at this point in the history
Make tuple Read/Write instances available without an import
  • Loading branch information
jatcwang authored Jul 27, 2024
2 parents 76d8883 + 0e05bea commit 4324be7
Show file tree
Hide file tree
Showing 21 changed files with 171 additions and 25 deletions.
1 change: 1 addition & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ ThisBuild / tlBaseVersion := "1.0"
ThisBuild / tlCiReleaseBranches := Seq("main") // publish snapshots on `main`
ThisBuild / tlCiScalafmtCheck := true
ThisBuild / scalaVersion := scala213Version
//ThisBuild / scalaVersion := scala3Version
ThisBuild / crossScalaVersions := Seq(scala212Version, scala213Version, scala3Version)
ThisBuild / developers += tlGitHubDev("tpolecat", "Rob Norris")
ThisBuild / tpolecatDefaultOptionsMode :=
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ package doobie.util
import shapeless.{HList, HNil, ::, Generic, Lazy, <:!<, OrElse}
import shapeless.labelled.{field, FieldType}

trait ReadPlatform extends LowerPriorityRead {
trait MkReadPlatform extends LowerPriorityRead {

// Derivation base case for product types (1-element)
implicit def productBase[H](
Expand Down Expand Up @@ -65,8 +65,8 @@ trait LowerPriorityRead extends EvenLowerPriorityRead {
}

// Derivation for product types (i.e. case class)
implicit def generic[F, G](implicit gen: Generic.Aux[F, G], G: Lazy[MkRead[G]]): MkRead[F] =
new MkRead[F](G.value.gets, (rs, n) => gen.from(G.value.unsafeGet(rs, n)))
implicit def generic[T, Repr](implicit gen: Generic.Aux[T, Repr], G: Lazy[MkRead[Repr]]): MkRead[T] =
new MkRead[T](G.value.gets, (rs, n) => gen.from(G.value.unsafeGet(rs, n)))

// Derivation base case for Option of product types (1-element)
implicit def optProductBase[H](
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ package doobie.util
import shapeless.{HList, HNil, ::, Generic, Lazy, <:!<, OrElse}
import shapeless.labelled.{FieldType}

trait WritePlatform extends LowerPriorityWrite {
trait MkWritePlatform extends LowerPriorityWrite {

// Derivation base case for product types (1-element)
implicit def productBase[H](
Expand Down
32 changes: 32 additions & 0 deletions modules/core/src/main/scala-2/doobie/util/ReadPlatform.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright (c) 2013-2020 Rob Norris and Contributors
// This software is licensed under the MIT License (MIT).
// For more information see LICENSE or https://opensource.org/licenses/MIT

package doobie.util

import shapeless.{Generic, HList, IsTuple, Lazy}

trait ReadPlatform {

// Derivation for product types (i.e. case class)
implicit def genericTuple[A, Repr <: HList](implicit
gen: Generic.Aux[A, Repr],
G: Lazy[MkRead[Repr]],
isTuple: IsTuple[A]
): MkRead[A] = {
val _ = isTuple
MkRead.generic[A, Repr]
}

// Derivation for optional of product types (i.e. case class)
implicit def ogenericTuple[A, Repr <: HList](
implicit
G: Generic.Aux[A, Repr],
B: Lazy[MkRead[Option[Repr]]],
isTuple: IsTuple[A]
): MkRead[Option[A]] = {
val _ = isTuple
MkRead.ogeneric[A, Repr]
}

}
30 changes: 30 additions & 0 deletions modules/core/src/main/scala-2/doobie/util/WritePlatform.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright (c) 2013-2020 Rob Norris and Contributors
// This software is licensed under the MIT License (MIT).
// For more information see LICENSE or https://opensource.org/licenses/MIT

package doobie.util

import shapeless.{Generic, HList, IsTuple, Lazy}

trait WritePlatform {

implicit def genericTuple[A, Repr](
implicit
gen: Generic.Aux[A, Repr],
A: Lazy[MkWrite[Repr]],
isTuple: IsTuple[A]
): MkWrite[A] = {
val _ = isTuple
MkWrite.generic[A, Repr]
}

implicit def ogenericTuple[A, Repr <: HList](
implicit
G: Generic.Aux[A, Repr],
A: Lazy[MkWrite[Option[Repr]]],
isTuple: IsTuple[A]
): MkWrite[Option[A]] = {
val _ = isTuple
MkWrite.ogeneric[A, Repr]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ package doobie.util
import scala.deriving.Mirror
import doobie.util.shapeless.OrElse

trait ReadPlatform:
trait MkReadPlatform:

// Generic Read for products.
given derived[P <: Product, A](
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,27 @@ package doobie.util
import scala.deriving.Mirror
import doobie.util.shapeless.OrElse

trait WritePlatform:
trait MkWritePlatform:

// Derivation for product types (i.e. case class)
given derived[P <: Product, A](
using
m: Mirror.ProductOf[P],
i: m.MirroredElemTypes =:= A,
w: MkWrite[A]
): MkWrite[P] = {
): MkWrite[P] =
val write: Write[P] = w.contramap(p => i(Tuple.fromProductTyped(p)))
MkWrite.lift(write)
}

// Derivation for optional product types
given derivedOption[P <: Product, A](
using
m: Mirror.ProductOf[P],
i: m.MirroredElemTypes =:= A,
w: MkWrite[Option[A]]
): MkWrite[Option[P]] = {
): MkWrite[Option[P]] =
val write: Write[Option[P]] = w.contramap(op => op.map(p => i(Tuple.fromProductTyped(p))))
MkWrite.lift(write)
}

// Derivation base case for product types (1-element)
given productBase[H](
Expand Down
26 changes: 26 additions & 0 deletions modules/core/src/main/scala-3/doobie/util/ReadPlatform.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) 2013-2020 Rob Norris and Contributors
// This software is licensed under the MIT License (MIT).
// For more information see LICENSE or https://opensource.org/licenses/MIT

package doobie.util

import scala.deriving.Mirror

trait ReadPlatform:
// Generic Read for products.
given derivedTuple[P <: Tuple, A](
using
m: Mirror.ProductOf[P],
i: A =:= m.MirroredElemTypes,
w: MkRead[A]
): MkRead[P] =
MkRead.derived[P, A]

// Generic Read for option of products.
given derivedOptionTuple[P <: Tuple, A](
using
m: Mirror.ProductOf[P],
i: A =:= m.MirroredElemTypes,
w: MkRead[Option[A]]
): MkRead[Option[P]] =
MkRead.derivedOption[P, A]
27 changes: 27 additions & 0 deletions modules/core/src/main/scala-3/doobie/util/WritePlatform.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) 2013-2020 Rob Norris and Contributors
// This software is licensed under the MIT License (MIT).
// For more information see LICENSE or https://opensource.org/licenses/MIT

package doobie.util

import scala.deriving.Mirror

trait WritePlatform:

// Derivation for product types (i.e. case class)
given derivedTuple[P <: Tuple, A](
using
m: Mirror.ProductOf[P],
i: m.MirroredElemTypes =:= A,
w: MkWrite[A]
): MkWrite[P] =
MkWrite.derived[P, A]

// Derivation for optional product types
given derivedOptionTuple[P <: Tuple, A](
using
m: Mirror.ProductOf[P],
i: m.MirroredElemTypes =:= A,
w: MkWrite[Option[A]]
): MkWrite[Option[P]] =
MkWrite.derivedOption[P, A]
10 changes: 6 additions & 4 deletions modules/core/src/main/scala/doobie/util/read.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
package doobie.util

import cats.*
import doobie.free.{ResultSetIO}
import doobie.free.ResultSetIO
import doobie.enumerated.Nullability.*

import java.sql.ResultSet
import scala.annotation.implicitNotFound
import doobie.free.{resultset as IFRS}
import doobie.free.resultset as IFRS

@implicitNotFound("""
Cannot find or construct a Read instance for type:
Expand Down Expand Up @@ -59,7 +60,7 @@ sealed abstract class Read[A](

}

object Read {
object Read extends ReadPlatform {

def apply[A](
gets: List[(Get[?], NullabilityKnown)],
Expand Down Expand Up @@ -99,7 +100,8 @@ final class MkRead[A](
override val gets: List[(Get[?], NullabilityKnown)],
override val unsafeGet: (ResultSet, Int) => A
) extends Read[A](gets, unsafeGet)
object MkRead extends ReadPlatform {

object MkRead extends MkReadPlatform {

def lift[A](r: Read[A]): MkRead[A] =
new MkRead[A](r.gets, r.unsafeGet)
Expand Down
4 changes: 2 additions & 2 deletions modules/core/src/main/scala/doobie/util/write.scala
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ sealed abstract class Write[A](

}

object Write {
object Write extends WritePlatform {

def apply[A](
puts: List[(Put[?], NullabilityKnown)],
Expand Down Expand Up @@ -153,7 +153,7 @@ final class MkWrite[A](
override val unsafeSet: (PreparedStatement, Int, A) => Unit,
override val unsafeUpdate: (ResultSet, Int, A) => Unit
) extends Write[A](puts, toList, unsafeSet, unsafeUpdate)
object MkWrite extends WritePlatform {
object MkWrite extends MkWritePlatform {

def lift[A](w: Write[A]): MkWrite[A] =
new MkWrite[A](w.puts, w.toList, w.unsafeSet, w.unsafeUpdate)
Expand Down
2 changes: 1 addition & 1 deletion modules/core/src/test/scala/doobie/issue/780.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import doobie.testutils.VoidExtensions

import scala.annotation.nowarn

@nowarn("msg=.*(Foo is never used|unused).*")
@nowarn("msg=.*(Foo is never used|(U|u)nused).*")
class `780` extends munit.FunSuite {
import doobie.generic.auto.*

Expand Down
21 changes: 17 additions & 4 deletions modules/core/src/test/scala/doobie/util/ReadSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,23 @@ class ReadSuite extends munit.FunSuite with ReadSuitePlatform {
assert(compileErrors("Read[Option[CaseObj.type]]").contains("Cannot find or construct"))
}

test("Read is not auto derived for tuples without an import") {
assert(compileErrors("Read[(Int, Int)]").contains("Cannot find or construct"))
assert(compileErrors("Read[(Int, Int, String)]").contains("Cannot find or construct"))
assert(compileErrors("Read[(Int, (Int, String))]").contains("Cannot find or construct"))
test("Read is auto derived for tuples without an import") {
Read[(Int, Int)].void
Read[(Int, Int, String)].void
Read[(Int, (Int, String))].void

Read[Option[(Int, Int)]].void
Read[Option[(Int, Option[(String, Int)])]].void
}

test("Read is still auto derived for tuples when import is present (no ambiguous implicits)") {
import doobie.generic.auto.*
Read[(Int, Int)].void
Read[(Int, Int, String)].void
Read[(Int, (Int, String))].void

Read[Option[(Int, Int)]].void
Read[Option[(Int, Option[(String, Int)])]].void
}

test("Read can be manually derived") {
Expand Down
21 changes: 17 additions & 4 deletions modules/core/src/test/scala/doobie/util/WriteSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,23 @@ class WriteSuite extends munit.FunSuite with WriteSuitePlatform {
Write[ComplexCaseClass].void
}

test("Write is not auto derived for tuples without an import") {
assert(compileErrors("Write[(Int, Int)]").contains("Cannot find or construct"))
assert(compileErrors("Write[(Int, Int, String)]").contains("Cannot find or construct"))
assert(compileErrors("Write[(Int, (Int, String))]").contains("Cannot find or construct"))
test("Write is auto derived for tuples without an import") {
Write[(Int, Int)].void
Write[(Int, Int, String)].void
Write[(Int, (Int, String))].void

Write[Option[(Int, Int)]].void
Write[Option[(Int, Option[(String, Int)])]].void
}

test("Write is still auto derived for tuples when import is present (no ambiguous implicits) ") {
import doobie.generic.auto.*
Write[(Int, Int)].void
Write[(Int, Int, String)].void
Write[(Int, (Int, String))].void

Write[Option[(Int, Int)]].void
Write[Option[(Int, Option[(String, Int)])]].void
}

test("Write is not auto derived for case classes") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ object TimeArbitraries {
// 1000-01-01 to 9999-12-31
implicit val arbitraryLocalDate: Arbitrary[LocalDate] = Arbitrary {
GenHelpers.chooseT(LocalDate.of(1000, 1, 1), LocalDate.of(9999, 12, 31), LocalDate.of(1970, 1, 1))
.filter(date =>
// Remove invalid 29th February
!date.isLeapYear || date.getMonth != Month.FEBRUARY || date.getDayOfMonth != 29)
}

// 00:00:00.000000 to 23:59:59.999999
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ object TimeArbitraries {
implicit val arbitraryLocalDateTime: Arbitrary[LocalDateTime] = Arbitrary {
for {
date <- GenHelpers.chooseT(MinTimestampDate, MaxTimestampDate)
if date.isLeapYear || date.getMonth != Month.FEBRUARY || date.getDayOfMonth != 29
time <- arbitraryLocalTime.arbitrary
} yield LocalDateTime.of(date, time)
}
Expand Down

0 comments on commit 4324be7

Please sign in to comment.