Skip to content

Commit

Permalink
Create extension methods for onSubtype/on.../eachItem/each... in DSL
Browse files Browse the repository at this point in the history
  • Loading branch information
MateuszKubuszok committed Apr 7, 2024
1 parent 8a94eaa commit af1638b
Show file tree
Hide file tree
Showing 12 changed files with 135 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,10 @@ object fixtures {
}

object nonFunhappy {
def validateA(a: Int): Int = throw new Exception("a not nice")
def validateA(a: Int): Int = sys.error("a not nice")
def validateB(b: Double): Double = b
def validateC(c: String): String = throw new Exception("c not pretty")
def validateD(d: Option[String]): Option[String] = throw new Exception("I don't like this d")
def validateC(c: String): String = sys.error("c not pretty")
def validateD(d: Option[String]): Option[String] = sys.error("I don't like this d")
}

object happy {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ package object dsl {
implicit def TryPartialTransformerOps[A](`try`: Try[A]): syntax.TryPartialTransformerOps[A] =
syntax.TryPartialTransformerOps(`try`)

implicit def TransformationPathOps[A](a: A): syntax.TransformationPathOps[A] =
syntax.TransformationPathOps(a)

// Extension methods in dsl.* summon TypeClass.AutoDerived while extension methods in syntax.* summon TypeClass.
// This help us preserve legacy behavior in dsl code while keeping stricter separation in auto/syntax imports.

Expand Down
28 changes: 28 additions & 0 deletions chimney/src/main/scala-2/io/scalaland/chimney/syntax/package.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package io.scalaland.chimney

import io.scalaland.chimney.internal.runtime.{IsCollection, IsEither, IsMap, IsOption}

import scala.annotation.unused
import scala.util.Try

/** Imports only extension methods for summoning and using Transformer, PartialTransformer or Patcher
Expand Down Expand Up @@ -177,4 +180,29 @@ package object syntax {
def toPartialResult: partial.Result[A] =
partial.Result.fromTry(`try`)
}

// TODO: docs
implicit final class TransformationPathOps[A](@unused private val a: A) extends AnyVal {

def onSubtype[B <: A]: B =
sys.error(".onSubtype should be only called within Chimney DSL")

def onSome[B](implicit @unused ev: IsOption.Of[A, B]): B =
sys.error(".onSome should be only called within Chimney DSL")

def onLeft[L, R](implicit @unused ev: IsEither.Of[A, L, R]): L =
sys.error(".onLeft should be only called within Chimney DSL")

def onRight[L, R](implicit @unused ev: IsEither.Of[A, L, R]): R =
sys.error(".onRight should be only called within Chimney DSL")

def eachItem[B](implicit @unused ev: IsCollection.Of[A, B]): B =
sys.error(".eachItem should be only called within Chimney DSL")

def eachMapKey[K, V](implicit @unused ev: IsMap.Of[K, V, A]): K =
sys.error(".eachMapKey should be only called within Chimney DSL")

def eachMapValue[K, V](implicit @unused ev: IsMap.Of[K, V, A]): V =
sys.error(".eachMapValue should be only called within Chimney DSL")
}
}
12 changes: 11 additions & 1 deletion chimney/src/main/scala-3/io/scalaland/chimney/dsl/dsl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,17 @@ import io.scalaland.chimney.internal.compiletime.derivation.transformer.Transfor
import io.scalaland.chimney.internal.compiletime.derivation.patcher.PatcherMacros

export io.scalaland.chimney.inlined.{into, intoPartial, using}
export io.scalaland.chimney.syntax.{toPartialResult, toPartialResultOrString}
export io.scalaland.chimney.syntax.{
eachItem,
eachMapKey,
eachMapValue,
onLeft,
onRight,
onSome,
onSubtype,
toPartialResult,
toPartialResultOrString
}

// Extension methods in dsl.* summon TypeClass.AutoDerived while extension methods in syntax.* summon TypeClass.
// This help us preserve legacy behavior in dsl code while keeping stricter separation in auto/syntax imports.
Expand Down
26 changes: 26 additions & 0 deletions chimney/src/main/scala-3/io/scalaland/chimney/syntax/syntax.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package io.scalaland.chimney.syntax

import io.scalaland.chimney.{partial, PartialTransformer, Patcher, Transformer}
import io.scalaland.chimney.internal.runtime.{IsCollection, IsEither, IsMap, IsOption}

import scala.annotation.unused
import scala.util.Try

// Extension methods in dsl.* summon TypeClass.AutoDerived while extension methods in syntax.* summon TypeClass.
Expand Down Expand Up @@ -172,3 +174,27 @@ extension [T](`try`: Try[T]) {
transparent inline def toPartialResult: partial.Result[T] =
partial.Result.fromTry(`try`)
}

extension [A](@unused a: A) {

def onSubtype[B <: A]: B =
sys.error(".onSubtype should be only called within Chimney DSL")

def onSome[B](implicit @unused ev: IsOption.Of[A, B]): B =
sys.error(".onSome should be only called within Chimney DSL")

def onLeft[L, R](implicit @unused ev: IsEither.Of[A, L, R]): L =
sys.error(".onLeft should be only called within Chimney DSL")

def onRight[L, R](implicit @unused ev: IsEither.Of[A, L, R]): R =
sys.error(".onRight should be only called within Chimney DSL")

def eachItem[B](implicit @unused ev: IsCollection.Of[A, B]): B =
sys.error(".eachItem should be only called within Chimney DSL")

def eachMapKey[K, V](implicit @unused ev: IsMap.Of[K, V, A]): K =
sys.error(".eachMapKey should be only called within Chimney DSL")

def eachMapValue[K, V](implicit @unused ev: IsMap.Of[K, V, A]): V =
sys.error(".eachMapValue should be only called within Chimney DSL")
}
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ private[compiletime] trait Configurations { this: Derivation =>
case _ => false
}

override def toString: String = s".whenSubtype[${Type.prettyPrint(tpe.Underlying)}]"
override def toString: String = s".onSubtype[${Type.prettyPrint(tpe.Underlying)}]"
}
case object EachItem extends Segment {
override def toString: String = ".eachItem"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.scalaland.chimney.internal.runtime

import scala.annotation.unused

sealed trait IsCollection[C] {
type Item
}
object IsCollection {
type Of[C, A] = IsCollection[C] { type Item = A }

private object Impl extends IsCollection[Nothing]

// build-in Chimney support for collections assumes that they are BOTH Iterable and have a Factory
implicit def scalaCollectionIsCollection[A, C <: Iterable[A]](implicit
@unused ev: scala.collection.compat.Factory[A, C]
): IsCollection.Of[C, A] = Impl.asInstanceOf[IsCollection.Of[C, A]]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.scalaland.chimney.internal.runtime

sealed trait IsEither[E] {
type Left
type Right
}
object IsEither {

type Of[E, L, R] = IsEither[E] { type Left = L; type Right = R }

private object Impl extends IsEither[Nothing]

implicit def eitherOsEither[L, R, E <: Left[L, R]]: IsEither.Of[E, L, R] = Impl.asInstanceOf[IsEither.Of[E, L, R]]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.scalaland.chimney.internal.runtime
import scala.annotation.unused

sealed trait IsMap[M] {
type Key
type Value
}
object IsMap {
type Of[M, K, V] = IsMap[M] { type Key = K; type Value = V }

private object Impl extends IsMap[Nothing]

// build-in Chimney support for maps assumes that they are BOTH Map and have a Factory
implicit def scalaMapIsMap[K, V, M <: Map[K, V]](implicit
@unused ev: scala.collection.compat.Factory[(K, V), M]
): IsMap.Of[K, V, M] = Impl.asInstanceOf[IsMap.Of[K, V, M]]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package io.scalaland.chimney.internal.runtime

sealed trait IsOption[O] {
type Value
}
object IsOption {

type Of[O, A] = IsOption[O] { type Value = A }

private object Impl extends IsOption[Nothing]

implicit def optionIsOption[A, O <: Option[A]]: IsOption.Of[O, A] = Impl.asInstanceOf[IsOption.Of[O, A]]
}
Original file line number Diff line number Diff line change
Expand Up @@ -756,3 +756,4 @@ class IssuesSpec extends ChimneySpec {
.transform ==> To(uuid1, uuid2, "test")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ class PartialTransformerProductSpec extends ChimneySpec {

val result7 = Person("John", 10, 140)
.intoPartial[User]
.withFieldComputed(_.age, _ => throw new Exception("error happened"))
.withFieldComputed(_.age, _ => sys.error("error happened"))
.transform
result7.asOption ==> None
result7.asEither.isLeft ==> true
Expand Down

0 comments on commit af1638b

Please sign in to comment.