scala-newtype
scala-newtype copied to clipboard
Illegal cyclic reference error when abstract companion
I suppose the companion objects aren't working as expected
when they have a type hierarchy.
Consider a simple example:
- I have some re-usable companion:
abstract class AbstractObjectCompanion[T] {
def apply(src: Int): T
implicit class Ops(t: T) {
def print = t.toString
}
}
- And its implementation here:
import io.estatico.newtype.macros.newtype
package object some {
@newtype class MyInt(val i: Int)
object MyInt extends AbstractObjectCompanion[MyInt] {
override def apply(src: Int): MyInt = new MyInt(src)
}
}
... does not compile:
illegal cyclic reference involving type MyInt
object MyInt extends AbstractObjectCompanion[MyInt] {
But it compiles without @newtype
OR without AbstractObjectCompanion.
Out of interest I looked at this, the macro desugars to:
package object some {
type MyInt = MyInt.Type
object MyInt extends AbstractObjectCompanion[MyInt] {
type Base = Any { type __MyInt__newtype }
abstract trait Tag extends Any
type Type <: Base with Tag
}
}
(ignoring other details) which is just cyclic. I guess Base/Tag could be obfuscated a bit (__MyInt__
prefixed) and moved out, and just define the external Type.
I'm guessing that the compiler must have ways to deal with this when dealing with companion classes and companion objects in a cyclical relationship..
That begs the question, have you tried the following?
abstract class AbstractObjectCompanion {
type Type
def apply(src: Int): Type
implicit class Ops(t: Type) {
def print = t.toString
}
}
Sorry to bump this old issue, but just ran into this exact situation. I want to extract some boilerplate fromString[...]
functions into a base class for pretty much the same reason as the OP, and I get the illegal cyclic reference error.
~I tried using a type member as @joroKr21 suggested, and it seems to work, btw! But now it's slightly more ugly :)~ I misread, and apparently making it a type member is even cleaner! Thanks @joroKr21!
@newtype final case class Foo private (x: String)
object Foo extends MyBase(...)
abstract class MyBase(...) {
type Type
def fromString(s: String)(implicit ev: Coercible[String, Type]): Either[String, Type] =
alphanumeric(s).map(_.coerce[Type])
}
(using abstract class here to pass additional ctor parameters needed for other things)
This gives me:
> Foo.fromString("5")
Right(5)
as expected.
Leaving this here for anyone who would run into this issue...
p.s. could not name the type parameter Type because it clashed with what the macro expands to :)
That's the whole point, if you leave it abstract Type
will be defined by object Foo
This works for me: https://scastie.scala-lang.org/eQwfMXkJQA252pPolKxUHQ
D'oh! Of course :) Haha this is even better! Thanks @joroKr21!