Skip to content

Commit

Permalink
Add some short explanation to why different structures exists
Browse files Browse the repository at this point in the history
  • Loading branch information
MateuszKubuszok committed Jul 26, 2023
1 parent 13776b8 commit b9cbd2d
Show file tree
Hide file tree
Showing 19 changed files with 70 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package io.scalaland.chimney.internal.compiletime

private[compiletime] trait Existentials { this: Types with Exprs =>

/** Represents value with some existential type and Type using the same existential.
/** Represents value with some existential type t both for Type[t] as well as F[t].
*
* Since Scala 3 removed a lot of cases for existential types we cannot just use Type[?] in shared code.
* Additionally, we might need to have something to prove that our Type[?] is has the same ? as some Value[?].
Expand Down Expand Up @@ -85,6 +85,8 @@ private[compiletime] trait Existentials { this: Types with Exprs =>
type Underlying = A
}

// aliases to make the (very common) existential types shorter

type ?? = ExistentialType
type ?>[L] = ExistentialType.LowerBounded[L]
type ?<[U] = ExistentialType.UpperBounded[U]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ private[compiletime] trait Exprs { this: Definitions =>

def tpe: Type[A] = Expr.typeOf(expr)

/** Creates '{ $expr == $other } expression, which would compare both expressions in runtime */
def eqExpr[B: Type](other: Expr[B]): Expr[Boolean] = Expr.eq(expr, other)

// All of methods below change Expr[A] to Expr[B], but they differ in checks ans how it affects the underlying code:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ private[compiletime] trait Results { this: Definitions =>
/** Prints info at current macro expansion - assume it can only be called once */
protected def reportInfo(info: String): Unit

/** Prints error at current macro expansion AND throw exception for aborting macro expanion */
/** Prints error at current macro expansion AND throw exception for aborting macro expansion */
protected def reportError(errors: String): Nothing

/** Throws AssertionFailed exception */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ private[compiletime] trait Types { this: Existentials =>
protected type Type[A]
protected val Type: TypeModule
protected trait TypeModule { this: Type.type =>
/** Summons Type instance */
final def apply[A](implicit A: Type[A]): Type[A] = A

// Interfaces for applying and extracting type parameters in shared code
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ trait IterableOrArrays { this: Definitions =>

import Type.Implicits.*

/** Something allowing us to dispatch same-looking-source-code-but-different ASTs for Iterables and Arrays */
/** Something allowing us to dispatch same-looking-source-code-but-different ASTs for Iterables and Arrays.
*
* Exists because `Array` is NOT `Iterable`, and all operations like `.map`, `.to`, etc are done through extension
* methods. Meanwhile, we would like to be able to convert to and from Array easily.
*/
abstract protected class IterableOrArray[M, A] {
def iterator(m: Expr[M]): Expr[Iterator[A]]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,21 @@ import scala.collection.immutable.ListMap

trait ProductTypes { this: Definitions =>

/** Describes all types which could be considered products in a very loose way.
*
* For type to be considered "product" it has to be:
* - non abstract
* - have a public (primary) constructor
*
* If it's a "product" then we are able to provide both a way to construct it as well as a way to extract its
* properties. This is rather unrestricted since:
* - our "constructor" allows passing arguments to Java Bean setters
* - our properties include: `def`s without arguments, Java Bean getters
* and it's the code using the extractors and constructors that should check the type of getter/constructor argument.
*
* In case we don't need a "product" per se, but rather any instantiable type to instantiate or any type to obtain
* its methods, we can use `unapply` from `Extraction` or `Construction`.
*/
final protected case class Product[A](extraction: Product.Extraction[A], construction: Product.Constructor[A])
protected object Product {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import io.scalaland.chimney.internal.compiletime.Definitions

trait SealedHierarchies { this: Definitions =>

/** Let us obtain a list of types implementing the sealed hierarchy */
/** Let us obtain a list of types implementing the sealed hierarchy.
*
* It describes both Scala 2's "normal" sealed hierarchies as well as Scala 3's enums.
*/
final protected case class Enum[A](elements: Enum.Elements[A])
protected object Enum {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,16 @@ import io.scalaland.chimney.internal.compiletime.Definitions

trait ValueClasses { this: Definitions =>

/** Let us unwrap and wrap value in any class that wraps a single value (not only AnyVals) */
/** Let us unwrap and wrap value in any class that wraps a single value (not only AnyVals)
*
* For a class to be considered wrapper it has to:
* - have a public unary constructor
* - expose a getter of the same name and type as constructor's argument
*
* Basically, it is a value class without the need to extends AnyVal. This is useful since sometimes we have a type
* which is basically a wrapper but not an AnyVal and we would like to unwrap it and attempt to derive code as if it
* was AnyVal. Since it is very contextual, we need to have a separate utility for that.
*/
final protected case class WrapperClass[Outer, Inner](
fieldName: String,
unwrap: Expr[Outer] => Expr[Inner],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.scalaland.chimney.internal.compiletime

/** Gathers all possible derivation errors in a single type */
sealed trait DerivationError extends Product with Serializable
object DerivationError {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.scalaland.chimney.internal.compiletime

/** Represents a single log position in a log journal */
sealed private[compiletime] trait Log extends Product with Serializable
private[compiletime] object Log {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package io.scalaland.chimney.internal.compiletime

/** Patcher-specific error related to derivation logic */
sealed trait PatcherDerivationError extends Product with Serializable

case class NotSupportedPatcherDerivation(objTypeName: String, patchTypeName: String) extends PatcherDerivationError
final case class NotSupportedPatcherDerivation(objTypeName: String, patchTypeName: String) extends PatcherDerivationError

case class PatchFieldNotFoundInTargetObj(patchFieldName: String, objTypeName: String) extends PatcherDerivationError
final case class PatchFieldNotFoundInTargetObj(patchFieldName: String, objTypeName: String) extends PatcherDerivationError

object PatcherDerivationError {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.scalaland.chimney.internal.compiletime

/** Transformer-specific error related to derivation logic */
sealed trait TransformerDerivationError extends Product with Serializable {
def sourceTypeName: String
def targetTypeName: String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,14 @@ import io.scalaland.chimney.internal.compiletime.{Definitions, DerivationResult}

private[compiletime] trait GatewayCommons { this: Definitions =>

/** Assigns `expr` value to newly created val, and then uses reference to the reference to this val.
*
* It avoids recalculating the same expression in the runtime, "stabilizes" the val if it's needed, etc.
*/
protected def cacheDefinition[A: Type, Out: Type](expr: Expr[A])(usage: Expr[A] => Expr[Out]): Expr[Out] =
PrependDefinitionsTo.prependVal[A](expr, ExprPromise.NameGenerationStrategy.FromType).use(usage)

/** Let us keep the information if logging is needed in code that never had access to Context. */
protected def enableLoggingIfFlagEnabled[A](
result: DerivationResult[A],
isMacroLoggingEnabled: Boolean,
Expand All @@ -15,6 +20,7 @@ private[compiletime] trait GatewayCommons { this: Definitions =>
if (isMacroLoggingEnabled) DerivationResult.enableLogPrinting(derivationStartedAt) >> result
else result

/** Unwraps the `result` and fails with error message or prints diagnostics (if needed) before returning expression */
protected def extractExprAndLog[Out: Type](result: DerivationResult[Expr[Out]], errorHeader: => String): Expr[Out] = {
result.state.macroLogging.foreach { case DerivationResult.State.MacroLogging(derivationStartedAt) =>
val duration = java.time.Duration.between(derivationStartedAt, java.time.Instant.now())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package io.scalaland.chimney.internal.compiletime.derivation.patcher

private[compiletime] trait Contexts { this: Derivation =>

/** Stores all the "global" information that might be needed: types used, user configuration, runtime values, etc */
final case class PatcherContext[A, Patch](
obj: Expr[A],
patch: Expr[Patch],
Expand All @@ -15,7 +16,6 @@ private[compiletime] trait Contexts { this: Derivation =>
final type Target = A
val Target = A
}

object PatcherContext {

def create[A: Type, Patch: Type](
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,13 @@ private[compiletime] trait Gateway extends GatewayCommons { this: Derivation =>

extractExprAndLog[A, Patch, Patcher[A, Patch]](result)
}

// TODO: name it better

private def enableLoggingIfFlagEnabled[A](
result: DerivationResult[A],
ctx: PatcherContext[?, ?]
): DerivationResult[A] =
enableLoggingIfFlagEnabled[A](result, ctx.config.displayMacrosLogging, ctx.derivationStartedAt)

// TODO: name it better

private def extractExprAndLog[A: Type, Patch: Type, Out: Type](result: DerivationResult[Expr[Out]]): Expr[Out] =
extractExprAndLog[Out](
result,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import io.scalaland.chimney.partial

private[compiletime] trait Contexts { this: Derivation =>

/** Stores all the "global" information that might be needed: types used, user configuration, runtime values, etc */
sealed protected trait TransformationContext[From, To] extends scala.Product with Serializable {
val src: Expr[From]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,7 @@ private[compiletime] trait Gateway extends GatewayCommons { this: Derivation =>
ctx: TransformationContext[?, ?]
): DerivationResult[A] =
enableLoggingIfFlagEnabled[A](result, ctx.config.flags.displayMacrosLogging, ctx.derivationStartedAt)

// TODO: name it better

private def extractExprAndLog[From: Type, To: Type, Out: Type](result: DerivationResult[Expr[Out]]): Expr[Out] =
extractExprAndLog[Out](
result,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import io.scalaland.chimney.partial

private[compiletime] trait ResultOps { this: Derivation =>

/** DerivationResult is defined outside the "cake", so methods using utilities from the cake have to be extensions */
implicit final protected class DerivationResultModule(derivationResult: DerivationResult.type) {

def existential[F[_], A: Type](fa: F[A]): DerivationResult[Existential[F]] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,21 @@ private[compiletime] trait TransformationRules { this: Derivation =>

import ChimneyType.Implicits.*

/** Defines "derivation rule" ("if condition is met then derive").
*
* Since we cannot restrict how condition is checked (running some predicate or using PartialFunction is too
* restrictive), we have to express matching or not with the result:
* - `Expanded` means that rule applied and created `Expr` value
* - `AttemptNextRule` means that rule decided that conditions aren't met
* The `DerivationResult` as a whole might also fail, which means that rule did apply but couldn't derive expression.
*/
abstract protected class Rule(val name: String) {

def expand[From, To](implicit ctx: TransformationContext[From, To]): DerivationResult[Rule.ExpansionResult[To]]
}

protected object Rule {

sealed trait ExpansionResult[+A]

object ExpansionResult {
// successfully expanded TransformationExpr
case class Expanded[A](transformationExpr: TransformationExpr[A]) extends ExpansionResult[A]
Expand All @@ -25,6 +31,7 @@ private[compiletime] trait TransformationRules { this: Derivation =>
case object AttemptNextRule extends ExpansionResult[Nothing]
}

/** Attempt to apply rules in order in which they are on list. The first match wins. */
def expandRules[From, To](
rules: List[Rule]
)(implicit ctx: TransformationContext[From, To]): DerivationResult[TransformationExpr[To]] = rules match {
Expand All @@ -47,6 +54,7 @@ private[compiletime] trait TransformationRules { this: Derivation =>
}
}

/** Let us store both `Expr[A]` and `Expr[partial.Result[A]]` as one type for convenience. */
sealed protected trait TransformationExpr[A] extends scala.Product with Serializable {

import TransformationExpr.{PartialExpr, TotalExpr}
Expand Down Expand Up @@ -118,8 +126,8 @@ private[compiletime] trait TransformationRules { this: Derivation =>
def exprPartition: Either[ExprPromise[From, Expr[To]], ExprPromise[From, Expr[partial.Result[To]]]] =
promise.map(_.toEither).partition

final def isTotal: Boolean = foldTransformationExpr(_ => true)(_ => false)
final def isPartial: Boolean = foldTransformationExpr(_ => false)(_ => true)
def isTotal: Boolean = foldTransformationExpr(_ => true)(_ => false)
def isPartial: Boolean = foldTransformationExpr(_ => false)(_ => true)

def ensureTotal: ExprPromise[From, Expr[To]] = promise.map(_.ensureTotal)
def ensurePartial: ExprPromise[From, Expr[partial.Result[To]]] = promise.map(_.ensurePartial)
Expand Down

0 comments on commit b9cbd2d

Please sign in to comment.