aboutsummaryrefslogtreecommitdiff
path: root/core/shared/src/main/scala/interface.scala
blob: 846eee9da1f6bc6e93370f8ed417f75a652058b1 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
package magnolia

import language.higherKinds
import scala.annotation.tailrec

/** represents a subtype of a sealed trait
  *
  *  @tparam Typeclass  type constructor for the typeclass being derived
  *  @tparam Type       generic type of this parameter */
trait Subtype[Typeclass[_], Type] {

  /** the type of subtype */
  type SType <: Type

  /** the [[TypeName]] of the subtype
    *
    *  This is the full name information for the type of subclass. */
  def typeName: TypeName

  /** the typeclass instance associated with this subtype
    *
    *  This is the instance of the type `Typeclass[SType]` which will have been discovered by
    *  implicit search, or derived by Magnolia. */
  def typeclass: Typeclass[SType]

  /** partial function defined the subset of values of `Type` which have the type of this subtype */
  def cast: PartialFunction[Type, SType]
}

/** represents a parameter of a case class
  *
  *  @tparam Typeclass  type constructor for the typeclass being derived
  *  @tparam Type       generic type of this parameter */
trait Param[Typeclass[_], Type] {

  /** the type of the parameter being represented
    *
    *  For example, for a case class,
    *  <pre>
    *  case class Person(name: String, age: Int)
    *  </pre>
    *  the [[Param]] instance corresponding to the `age` parameter would have a [[PType]] equal to
    *  the type [[scala.Int]]. However, in practice, this type will never be universally quantified.
    */
  type PType

  /** the name of the parameter */
  def label: String

  /** flag indicating a repeated (aka. vararg) parameter
    *
    * For example, for a case class,
    * <pre>
    * case class Account(id: String, emails: String*)
    * </pre>
    * the [[Param]] instance corresponding to the `emails` parameter would be `repeated` and have a
    * [[PType]] equal to the type `Seq[String]`. Note that only the last parameter of a case class
    * can be repeated. */
  def repeated: Boolean

  /** the typeclass instance associated with this parameter
    *
    *  This is the instance of the type `Typeclass[PType]` which will have been discovered by
    *  implicit search, or derived by Magnolia.
    *
    *  Its type is existentially quantified on this [[Param]] instance, and depending on the
    *  nature of the particular typeclass, it may either accept or produce types which are also
    *  existentially quantified on this same [[Param]] instance. */
  def typeclass: Typeclass[PType]

  /** provides the default value for this parameter, as defined in the case class constructor */
  def default: Option[PType]

  /** dereferences a value of the case class type, `Type`, to access the value of the parameter
    *  being represented
    *
    *  When programming generically, against an unknown case class, with unknown parameter names
    *  and types, it is not possible to directly access the parameter values without reflection,
    *  which is undesirable. This method, whose implementation is provided by the Magnolia macro,
    *  will dereference a case class instance to access the parameter corresponding to this
    *  [[Param]].
    *
    *  Whilst the type of the resultant parameter value cannot be universally known at the use, its
    *  type will be existentially quantified on this [[Param]] instance, and the return type of the
    *  corresponding `typeclass` method will be existentially quantified on the same value. This is
    *  sufficient for the compiler to determine that the two values are compatible, and the value may
    *  be applied to the typeclass (in whatever way that particular typeclass provides).
    *
    *  @param param  the instance of the case class to be dereferenced
    *  @return  the parameter value */
  def dereference(param: Type): PType
}

/** represents a case class or case object and the context required to construct a new typeclass
  *  instance corresponding to it
  *
  *  Instances of [[CaseClass]] provide access to all of the parameters of the case class, the full
  *  name of the case class type, and a boolean to determine whether the type is a case class or case
  *  object.
  *
  *  @param typeName         the name of the case class
  *  @param isObject         true only if this represents a case object rather than a case class
  *  @param parametersArray  an array of [[Param]] values for this case class
  *  @tparam Typeclass  type constructor for the typeclass being derived
  *  @tparam Type       generic type of this parameter */
abstract class CaseClass[Typeclass[_], Type] private[magnolia] (
  val typeName: TypeName,
  val isObject: Boolean,
  val isValueClass: Boolean,
  parametersArray: Array[Param[Typeclass, Type]]
) {

  /** constructs a new instance of the case class type
    *
    *  This method will be implemented by the Magnolia macro to make it possible to construct
    *  instances of case classes generically in user code, that is, without knowing their type
    *  concretely.
    *
    *  To construct a new case class instance, the method takes a lambda which defines how each
    *  parameter in the new case class should be constructed. See the [[Param]] class for more
    *  information on constructing parameter values from a [[Param]] instance.
    *
    *  @param makeParam  lambda for converting a generic [[Param]] into the value to be used for
    *                    this parameter in the construction of a new instance of the case class
    *  @return  a new instance of the case class */
  final def construct[Return](makeParam: Param[Typeclass, Type] => Return): Type =
    rawConstruct(parameters map makeParam)

  /** constructs a new instance of the case class type
    *
    *  Like [[construct]] this method is implemented by Magnolia and let's you construct case class
    *  instances generically in user code, without knowing their type concretely.
    *
    *  `rawConstruct`, however, is more low-level in that it expects you to provide a [[Seq]]
    *  containing all the field values for the case class type, in order and with the correct types.
    *
    * @param fieldValues contains the field values for the case class instance to be constructed,
    *                    in order and with the correct types.
    *  @return  a new instance of the case class
    *  @throws  IllegalArgumentException if the size of `paramValues` differs from the size of [[parameters]] */
  def rawConstruct(fieldValues: Seq[Any]): Type

  /** a sequence of [[Param]] objects representing all of the parameters in the case class
    *
    *  For efficiency, this sequence is implemented by an `Array`, but upcast to a
    *  [[scala.collection.Seq]] to hide the mutable collection API. */
  final def parameters: Seq[Param[Typeclass, Type]] = parametersArray
}

/** represents a sealed trait and the context required to construct a new typeclass instance
  *  corresponding to it
  *
  *  Instances of `SealedTrait` provide access to all of the component subtypes of the sealed trait
  *  which form a coproduct, and to the fully-qualified name of the sealed trait.
  *
  *  @param typeName       the name of the sealed trait
  *  @param subtypesArray  an array of [[Subtype]] instances for each subtype in the sealed trait
  *  @tparam Typeclass  type constructor for the typeclass being derived
  *  @tparam Type             generic type of this parameter */
final class SealedTrait[Typeclass[_], Type](val typeName: TypeName,
                                            subtypesArray: Array[Subtype[Typeclass, Type]]) {

  /** a sequence of all the subtypes of this sealed trait */
  def subtypes: Seq[Subtype[Typeclass, Type]] = subtypesArray

  /** convenience method for delegating typeclass application to the typeclass corresponding to the
    *  subtype of the sealed trait which matches the type of the `value`
    *
    *  @tparam Return  the return type of the lambda, which should be inferred
    *  @param value   the instance of the generic type whose value should be used to match on a
    *                 particular subtype of the sealed trait
    *  @param handle  lambda for applying the value to the typeclass for the particular subtype which
    *                 matches
    *  @return  the result of applying the `handle` lambda to subtype of the sealed trait which
    *           matches the parameter `value` */
  def dispatch[Return](value: Type)(handle: Subtype[Typeclass, Type] => Return): Return = {
    @tailrec def rec(ix: Int): Return =
      if (ix < subtypesArray.length) {
        val sub = subtypesArray(ix)
        if (sub.cast.isDefinedAt(value)) handle(sub) else rec(ix + 1)
      } else
        throw new IllegalArgumentException(
          s"The given value `$value` is not a sub type of `$typeName`"
        )
    rec(0)
  }
}

/**
  * Provides the different parts of a type's class name.
  */
final case class TypeName(owner: String, short: String) {
  def full: String = s"$owner.$short"
}

/**
  * This annotation can be attached to the implicit `gen` method of a type class companion,
  * which is implemented by the `Magnolia.gen` macro.
  * It causes magnolia to dump the macro-generated code to the console during compilation.
  *
  * @param typeNamePart If non-empty restricts the output generation to types
  *                     whose full name contains the given [[String]]
  */
final class debug(typeNamePart: String = "") extends scala.annotation.StaticAnnotation