scalalandio / chimney

@@ -12,6 +12,7 @@
Loading
12 12
    def toNameConstant: Constant = Constant(n.decodedName.toString)
13 13
    def toNameLiteral: Literal = Literal(toNameConstant)
14 14
    def toSingletonTpe: ConstantType = c.internal.constantType(toNameConstant)
15 +
    def toCanonicalName: String = n.toString
15 16
  }
16 17
17 18
  type TypeConstructorTag[F[_]] = WeakTypeTag[F[Unit]]
@@ -100,6 +101,11 @@
Loading
100 101
        // $COVERAGE-ON$
101 102
      }
102 103
    }
104 +
105 +
    def coproductSymbol: Symbol = t match {
106 +
      case c.universe.ConstantType(Constant(enumeration: TermSymbol)) => enumeration
107 +
      case _                                                          => t.typeSymbol
108 +
    }
103 109
  }
104 110
105 111
  implicit class SymbolOps(s: Symbol) {
@@ -143,11 +149,15 @@
Loading
143 149
    def typeInSealedParent(parentTpe: Type): Type = {
144 150
      s.typeSignature // Workaround for <https://issues.scala-lang.org/browse/SI-7755>
145 151
146 -
      val sEta = s.asType.toType.etaExpand
147 -
      sEta.finalResultType.substituteTypes(
148 -
        sEta.baseType(parentTpe.typeSymbol).typeArgs.map(_.typeSymbol),
149 -
        parentTpe.typeArgs
150 -
      )
152 +
      if (s.isJavaEnum) {
153 +
        s.typeSignature
154 +
      } else {
155 +
        val sEta = s.asType.toType.etaExpand
156 +
        sEta.finalResultType.substituteTypes(
157 +
          sEta.baseType(parentTpe.typeSymbol).typeArgs.map(_.typeSymbol),
158 +
          parentTpe.typeArgs
159 +
        )
160 +
      }
151 161
    }
152 162
  }
153 163

@@ -475,6 +475,18 @@
Loading
475 475
      }
476 476
  }
477 477
478 +
  private def getSealedHierarchyInstances(hierarchy: Type): Map[String, List[Symbol]] =
479 +
    if (hierarchy.typeSymbol.isJavaEnum) {
480 +
      hierarchy.companion.decls
481 +
        .filter(_.isJavaEnum)
482 +
        .toList
483 +
        .groupBy(_.name.toCanonicalName)
484 +
    } else {
485 +
      hierarchy.typeSymbol.classSymbolOpt.get.subclasses
486 +
        .map(_.typeInSealedParent(hierarchy).typeSymbol)
487 +
        .groupBy(_.name.toCanonicalName)
488 +
    }
489 +
478 490
  def expandSealedClasses(
479 491
      srcPrefixTree: Tree,
480 492
      config: TransformerConfig
@@ -485,59 +497,71 @@
Loading
485 497
        Right(instanceTree)
486 498
      }
487 499
      .getOrElse {
488 -
        val fromCS = From.typeSymbol.classSymbolOpt.get
489 -
        val toCS = To.typeSymbol.classSymbolOpt.get
490 -
491 -
        val fromInstances = fromCS.subclasses.map(_.typeInSealedParent(From))
492 -
        val toInstances = toCS.subclasses.map(_.typeInSealedParent(To))
493 -
494 -
        val targetNamedInstances = toInstances.groupBy(_.typeSymbol.name.toString)
495 -
496 -
        val instanceClauses = fromInstances.map { instTpe =>
497 -
          val instName = instTpe.typeSymbol.name.toString
498 -
499 -
          resolveCoproductInstance(srcPrefixTree, instTpe, To, config)
500 -
            .map { instanceTree =>
501 -
              Right(cq"_: $instTpe => $instanceTree")
502 -
            }
503 -
            .getOrElse {
504 -
              val instSymbol = instTpe.typeSymbol
505 -
              targetNamedInstances.getOrElse(instName, Nil) match {
506 -
                case List(matchingTargetTpe)
507 -
                    if (instSymbol.isModuleClass || instSymbol.isCaseClass) && matchingTargetTpe.typeSymbol.isModuleClass =>
508 -
                  val tree = mkTransformerBodyTree0(config) {
509 -
                    q"${matchingTargetTpe.typeSymbol.asClass.module}"
500 +
        val fromInstances = getSealedHierarchyInstances(From)
501 +
        val toInstances = getSealedHierarchyInstances(To)
502 +
503 +
        val instanceClauses = fromInstances.flatMap {
504 +
          case (canonicalName, instSymbols) =>
505 +
            instSymbols.map { instSymbol =>
506 +
              val instName = instSymbol.name.toString
507 +
              val instTpe = instSymbol.typeInSealedParent(From)
508 +
509 +
              resolveCoproductInstance(srcPrefixTree, instTpe, To, config)
510 +
                .map { instanceTree =>
511 +
                  Right(cq"_: $instTpe => $instanceTree")
512 +
                }
513 +
                .getOrElse {
514 +
                  toInstances.getOrElse(canonicalName, Nil) match {
515 +
                    case List(matchingTargetSymbol)
516 +
                        if (instSymbol.isModuleClass || instSymbol.isCaseClass) && matchingTargetSymbol.isModuleClass =>
517 +
                      val tree = mkTransformerBodyTree0(config) {
518 +
                        q"${matchingTargetSymbol.asClass.module}"
519 +
                      }
520 +
                      Right(cq"_: ${instSymbol.asType} => $tree")
521 +
                    case List(matchingTargetSymbol) if instSymbol.isCaseClass && matchingTargetSymbol.isCaseClass =>
522 +
                      val fn = freshTermName(instName)
523 +
                      expandDestinationCaseClass(Ident(fn), config.rec)(
524 +
                        instTpe,
525 +
                        matchingTargetSymbol.typeInSealedParent(To)
526 +
                      ).map { innerTransformerTree =>
527 +
                        cq"$fn: $instTpe => $innerTransformerTree"
528 +
                      }
529 +
                    case List(matchingTargetSymbol)
530 +
                        if (instSymbol.isModuleClass || instSymbol.isCaseClass) && matchingTargetSymbol.isJavaEnum => // sealed class may be parameterized
531 +
                      Right(cq"_: $instTpe => $matchingTargetSymbol")
532 +
                    case List(matchingTargetSymbol)
533 +
                        if instSymbol.isJavaEnum && matchingTargetSymbol.isModuleClass => // we do not support mapping from java enum to case classes, only to objects
534 +
                      // it is a bit too much of an effort to support java enum -> case class in general case as case class may carry properties, so we omit this by now
535 +
                      val tree = mkTransformerBodyTree0(config) {
536 +
                        q"${matchingTargetSymbol.asClass.module}"
537 +
                      }
538 +
                      Right(cq"_: $instSymbol => $tree")
539 +
                    case List(matchingTargetSymbol) if instSymbol.isJavaEnum && matchingTargetSymbol.isJavaEnum =>
540 +
                      Right(cq"_: $instSymbol => $matchingTargetSymbol")
541 +
                    case _ :: _ :: _ =>
542 +
                      Left {
543 +
                        Seq(
544 +
                          AmbiguousCoproductInstance(
545 +
                            instName,
546 +
                            From.typeSymbol.fullName,
547 +
                            To.typeSymbol.fullName
548 +
                          )
549 +
                        )
550 +
                      }
551 +
                    case _ =>
552 +
                      Left {
553 +
                        Seq(
554 +
                          CantFindCoproductInstanceTransformer(
555 +
                            instSymbol.fullName,
556 +
                            From.typeSymbol.fullName,
557 +
                            To.typeSymbol.fullName
558 +
                          )
559 +
                        )
560 +
                      }
510 561
                  }
511 -
                  Right(cq"_: ${instSymbol.asType} => $tree")
512 -
                case List(matchingTargetTpe) if instSymbol.isCaseClass && matchingTargetTpe.typeSymbol.isCaseClass =>
513 -
                  val fn = freshTermName(instName)
514 -
                  expandDestinationCaseClass(Ident(fn), config.rec)(instTpe, matchingTargetTpe)
515 -
                    .map { innerTransformerTree =>
516 -
                      cq"$fn: $instTpe => $innerTransformerTree"
517 -
                    }
518 -
                case _ :: _ :: _ =>
519 -
                  Left {
520 -
                    Seq(
521 -
                      AmbiguousCoproductInstance(
522 -
                        instName,
523 -
                        From.typeSymbol.fullName,
524 -
                        To.typeSymbol.fullName
525 -
                      )
526 -
                    )
527 -
                  }
528 -
                case _ =>
529 -
                  Left {
530 -
                    Seq(
531 -
                      CantFindCoproductInstanceTransformer(
532 -
                        instSymbol.fullName,
533 -
                        From.typeSymbol.fullName,
534 -
                        To.typeSymbol.fullName
535 -
                      )
536 -
                    )
537 -
                  }
538 -
              }
562 +
                }
539 563
            }
540 -
        }
564 +
        }.toSeq
541 565
542 566
        if (instanceClauses.forall(_.isRight)) {
543 567
          val clauses = instanceClauses.collect { case Right(clause) => clause }
@@ -559,23 +583,24 @@
Loading
559 583
      To: Type,
560 584
      config: TransformerConfig
561 585
  ): Option[Tree] = {
562 -
    if (config.wrapperType.isDefined && config.coproductInstancesF.contains((From.typeSymbol, To))) {
586 +
    val coproductSymbol = From.coproductSymbol
587 +
    if (config.wrapperType.isDefined && config.coproductInstancesF.contains((coproductSymbol, To))) {
563 588
      Some(
564 589
        mkCoproductInstance(
565 590
          config.transformerDefinitionPrefix,
566 591
          srcPrefixTree,
567 -
          From.typeSymbol,
592 +
          coproductSymbol,
568 593
          To,
569 594
          config.wrapperType
570 595
        )
571 596
      )
572 -
    } else if (config.coproductInstances.contains((From.typeSymbol, To))) {
597 +
    } else if (config.coproductInstances.contains((coproductSymbol, To))) {
573 598
      Some(
574 599
        mkTransformerBodyTree0(config) {
575 600
          mkCoproductInstance(
576 601
            config.transformerDefinitionPrefix,
577 602
            srcPrefixTree,
578 -
            From.typeSymbol,
603 +
            coproductSymbol,
579 604
            To,
580 605
            None
581 606
          )

@@ -65,12 +65,12 @@
Loading
65 65
      copy(fieldOverrides = fieldOverrides + (fieldName -> fieldOverride))
66 66
    }
67 67
68 -
    def coproductInstance(instanceType: Type, targetType: Type): TransformerConfig = {
69 -
      copy(coproductInstances = coproductInstances + (instanceType.typeSymbol -> targetType))
68 +
    def coproductInstance(instanceSymbol: Symbol, targetType: Type): TransformerConfig = {
69 +
      copy(coproductInstances = coproductInstances + (instanceSymbol -> targetType))
70 70
    }
71 71
72 -
    def coproductInstanceF(instanceType: Type, targetType: Type): TransformerConfig = {
73 -
      copy(coproductInstancesF = coproductInstancesF + (instanceType.typeSymbol -> targetType))
72 +
    def coproductInstanceF(instanceSymbol: Symbol, targetType: Type): TransformerConfig = {
73 +
      copy(coproductInstancesF = coproductInstancesF + (instanceSymbol -> targetType))
74 74
    }
75 75
  }
76 76
@@ -113,7 +113,7 @@
Loading
113 113
        .fieldOverride(fieldNameTo, FieldOverride.RenamedFrom(fieldNameFrom))
114 114
    } else if (cfgTpe.typeConstructor =:= coproductInstanceT) {
115 115
      val List(instanceType, targetType, rest) = cfgTpe.typeArgs
116 -
      captureTransformerConfig(rest).coproductInstance(instanceType, targetType)
116 +
      captureTransformerConfig(rest).coproductInstance(instanceType.coproductSymbol, targetType)
117 117
    } else if (cfgTpe.typeConstructor =:= wrapperTypeT) {
118 118
      val List(f, rest) = cfgTpe.typeArgs
119 119
      captureTransformerConfig(rest).copy(wrapperType = Some(f))
@@ -127,7 +127,7 @@
Loading
127 127
      captureTransformerConfig(rest).fieldOverride(fieldName, FieldOverride.ComputedF)
128 128
    } else if (cfgTpe.typeConstructor =:= coproductInstanceFT) {
129 129
      val List(instanceType, targetType, rest) = cfgTpe.typeArgs
130 -
      captureTransformerConfig(rest).coproductInstanceF(instanceType, targetType)
130 +
      captureTransformerConfig(rest).coproductInstanceF(instanceType.coproductSymbol, targetType)
131 131
    } else {
132 132
      // $COVERAGE-OFF$
133 133
      c.abort(c.enclosingPosition, "Bad internal transformer config type shape!")
Files Coverage
chimney/src/main/scala/io/scalaland/chimney 99.57%
Project Totals (25 files) 99.57%
Sunburst
The inner-most circle is the entire project, moving away from the center are folders then, finally, a single file. The size and color of each slice is representing the number of statements and the coverage, respectively.
Icicle
The top section represents the entire project. Proceeding with folders and finally individual files. The size and color of each slice is representing the number of statements and the coverage, respectively.
Grid
Each block represents a single file in the project. The size and color of each block is represented by the number of statements and the coverage, respectively.
Loading