Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Illegal cyclic reference error when abstract companion #67

Open
dmitry-worker opened this issue Feb 15, 2021 · 5 comments
Open

Illegal cyclic reference error when abstract companion #67

dmitry-worker opened this issue Feb 15, 2021 · 5 comments

Comments

@dmitry-worker
Copy link

I suppose the companion objects aren't working as expected when they have a type hierarchy.
Consider a simple example:

  1. I have some re-usable companion:
abstract class AbstractObjectCompanion[T] {
  def apply(src: Int): T
  implicit class Ops(t: T) {
    def print = t.toString
  }
}
  1. 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.

@dwijnand
Copy link

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..

@joroKr21
Copy link
Contributor

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
  }
}

@hmemcpy
Copy link

hmemcpy commented Dec 20, 2021

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...

@joroKr21
Copy link
Contributor

joroKr21 commented Dec 21, 2021

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

@hmemcpy
Copy link

hmemcpy commented Dec 21, 2021

D'oh! Of course :) Haha this is even better! Thanks @joroKr21!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants