In order to migrate all modules that use EnumType[] to scala3 (e.g. bigquery, avro) one aspect should be implemented in scala3 that is in place in scala2 implementation.
How Magnolia work with implicits in scala2
If compiler detects the same type of implicits imported from two scopes it fails with "could not find implicit value for parameter" error:
implicitly[Typeclass[SomeType]].act(...
/ \
/ \
[ import X._ ] [ import Y._ ]
/ \
/ \
object X { object Y {
implicit def imp1[T]: Typeclass[T] implicit def imp2[T]: Typeclass[T]
[ compilation error ]
could not find implicit value for parameter x: Typeclass[
If one of those imported instances requires another type class implicitly then compiler still fails treating both implicits with an equal priority:
implicitly[Typeclass[SomeType]].act(...
/ \
/ \
[ import X._ ] [ import Y._ ]
/ \
/ \
object X { object Y {
implicit def imp1[T]: Typeclass[T] implicit def imp2[T: AnotherTypeClass]: Typeclass[T]
[ compilation error ]
could not find implicit value for parameter x: Typeclass[
Even if implicit for AnotherTypeClass is not fulfilled, or even if it provided by native macro that aborts in such case:
implicit def anotherTypeClass[T]: AnotherTypeClass[T] = macro Macros.genMacro[T]
...
def genMacro[T: c.WeakTypeTag](c: whitebox.Context): c.Tree = {
import c.universe._
...
c.abort(c.enclosingPosition, "ERROR MESSAGE!")
...
}
It fails with the same error. It fails even if imp2 defined as depending on type class implemented in Magnolia:
implicit def imp2[T: MagnoliaTypeClass]: Typeclass[T]
Which is defined as:
type Typeclass[T] = MagnoliaTypeClass[T]
def join[T: AnotherTypeClass](caseClass: CaseClass[EnumType, T]): EnumType[T] = {
...
def split[T](sealedTrait: SealedTrait[EnumType, T]): EnumType[T] = {
That depends on AnotherTypeClass in the definition of def join[T: AnotherTypeClass]
But if the first implicit is defined with Magnolia macro as well then it is supposed to work like this:
imp2 is aborted in genMacro by some condition for T so imp1 is chosen
- if
T is product with fields of other types like Q, for that type imp2 is chosen
AnotherTypeClass is used to differentiate types and turn ON or OFF a macro.
Apparently this is how macros are generated to Java. Decompiled Java sources might be checked to see why and how exactly Magnolia is switching between implicits, because Scala itself doesn't provide this way.
How Magnolia work with implicits in scala3
If compiler detects the same type of implicits imported from two scopes it fails with "Ambiguous given instances" error:
summon[Typeclass[SomeType]].act(...
/ \
/ \
[ import X.given ] [ import Y.given ]
/ \
/ \
object X { object Y {
given imp1[T]: Typeclass[T] given imp2[T]: Typeclass[T]
[ compilation error ]
Ambiguous given instances: both given instance imp1 in object X and given instance imp2 in object Y match type...
If one of those imported instances requires another type class implicitly then compiler deprioritizes it:
summon[Typeclass[SomeType]].act(...
/ \
/ \
[ import X.given ] [ import Y.given ]
/ \
/ \
object X { object Y {
given imp1[T]: Typeclass[T] given imp2[T: AnotherTypeClass]: Typeclass[T]
[ THIS IS SELECTED ]
If type classes are implemented using Magnolia in scala3 we can't do the same trick and add dependency from join on AnotherTypeClass because those methods are implementing Magnolia's trait:
object MyDerivation extends ProductDerivation[TypeClass]:
def join[T](caseClass: CaseClass[TypeClass, T]): TypeClass[T] = ...
def split[T](sealedTrait: SealedTrait[TypeClass, T]): TypeClass[T] = ...
As opposed to duck typing approach in scala2. Even when intercepting Magnolia's derivedMirror like this:
summon[Typeclass[SomeType]].act(...
/ \
/ \
[ import X.given ] [ import Y.given ]
/ \
/ \
object X { object Y {
inline given imp1[T](using Mirror.Of[T]): inline given imp2[T: AnotherTypeClass]:
Typeclass[T] = MyDerivation.derivedMirror[T] Typeclass[T] = .... custom derived mirror code ...
we can't achieve the same using mirror: Mirror.Of[A] instance, or throwing an exception.
Potential solution may be trying to write completely unrelated to Magnolia instance using raw macros like in scala2 and aborting it. But the problem is that we can't make dependency from MagnoliaTypeClass to AnotherTypeClass like we did in scala2
In order to migrate all modules that use EnumType[] to scala3 (e.g. bigquery, avro) one aspect should be implemented in scala3 that is in place in scala2 implementation.
How Magnolia work with implicits in scala2
If compiler detects the same type of implicits imported from two scopes it fails with "could not find implicit value for parameter" error:
If one of those imported instances requires another type class implicitly then compiler still fails treating both implicits with an equal priority:
Even if implicit for
AnotherTypeClassis not fulfilled, or even if it provided by native macro that aborts in such case:It fails with the same error. It fails even if
imp2defined as depending on type class implemented in Magnolia:Which is defined as:
That depends on
AnotherTypeClassin the definition ofdef join[T: AnotherTypeClass]But if the first implicit is defined with Magnolia macro as well then it is supposed to work like this:
imp2is aborted ingenMacroby some condition forTsoimp1is chosenTis product with fields of other types likeQ, for that typeimp2is chosenAnotherTypeClassis used to differentiate types and turn ON or OFF a macro.Apparently this is how macros are generated to Java. Decompiled Java sources might be checked to see why and how exactly Magnolia is switching between implicits, because Scala itself doesn't provide this way.
How Magnolia work with implicits in scala3
If compiler detects the same type of implicits imported from two scopes it fails with "Ambiguous given instances" error:
If one of those imported instances requires another type class implicitly then compiler deprioritizes it:
If type classes are implemented using Magnolia in scala3 we can't do the same trick and add dependency from
joinonAnotherTypeClassbecause those methods are implementing Magnolia's trait:As opposed to duck typing approach in scala2. Even when intercepting Magnolia's
derivedMirrorlike this:we can't achieve the same using
mirror: Mirror.Of[A]instance, or throwing an exception.Potential solution may be trying to write completely unrelated to Magnolia instance using raw macros like in scala2 and aborting it. But the problem is that we can't make dependency from
MagnoliaTypeClasstoAnotherTypeClasslike we did in scala2