All Downloads are FREE. Search and download functionalities are using the official Maven repository.

smithy4s.deriving.EffectMirror.scala Maven / Gradle / Ivy

/*
 * Copyright 2024 Neandertech
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package smithy4s.deriving

import quoted.*

import scala.annotation.implicitNotFound

@implicitNotFound(
  "No Effect mirror could be generated. It may indicate that the target type is not a class/trait, or that the methods it contains return heterogenous effects.\n\nDiagnose any issues by calling EffectMirror.reify[T] directly"
)
sealed trait EffectMirror:
  type Effect[_]
  type MirroredType

// formatting config needs refining to work nicely with macros
// format: off

object EffectMirror:
  type Of[T] = EffectMirror { type MirroredType = T }

  transparent inline given reify[T]: Of[T] = ${ reifyImpl[T] }

  private def reifyImpl[T: Type](using Quotes): Expr[Of[T]] =
    import quotes.reflect.*

    val tpe = TypeRepr.of[T]
    val cls = tpe.classSymbol.get
    val decls = cls.declaredMethods.filterNot(internals.encodesDefaultParameter)

    // Split a type between an effect and a container
    def splitType(tpe: TypeRepr): (TypeRepr, Type[?]) = {
      val Id = TypeRepr.of[[A] =>> A]
      tpe match {
        case AppliedType(outer, inner) =>
          // we assume that iterables are part of the metamodel
          if (outer <:< TypeRepr.of[IterableOnce]) (Id, tpe.asType)
          else
            outer.asType match {
              case '[type f[_]; f] =>
                (outer, inner(0).asType)
              case _ => report.errorAndAbort(s"Types with complex kinds are not allowed : ${outer.show}")
            }
        case _ => (Id, tpe.asType)
      }
    }

    val effectsAndOps = decls.map(method =>
      tpe.memberType(method) match
        case ByNameType(res) => splitType(res)
        case MethodType(paramNames, paramTpes, res) =>
          res match
            case _: MethodType => report.errorAndAbort(s"curried method ${method.name} is not supported")
            case _: PolyType   => report.errorAndAbort(s"curried method ${method.name} is not supported")
            case _             => splitType(res)
        case _: PolyType => report.errorAndAbort(s"generic method ${method.name} is not supported")
    )
    val (effects, ops) = effectsAndOps.unzip
    val distinctEffects = effects.distinct
    val effect =
      if (distinctEffects.size == 0) Type.of[[A] =>> A]
      else if (distinctEffects.size > 1) {
        report.errorAndAbort(s"Heterogenous effects are unsupported: ${distinctEffects.map(_.show).mkString("; ")}")
      } else {
        distinctEffects.head.asType
      }

    effect match
      case '[type f[_]; f] =>
        '{
          (new EffectMirror { type Effect[A] = f[A]; type MirroredType = T }): EffectMirror.Of[T] {
            type Effect[A] = f[A]
          }
        }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy