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 error in case of mutually recursive, path-dependent types #7642

Open
scabug opened this issue Jul 8, 2013 · 10 comments
Open
Labels

Comments

@scabug
Copy link

scabug commented Jul 8, 2013

The following code:

trait SymbolLoaders {
  class SymbolLoader
  type LoadersPlatform <: Platform      { type PlatformLoaders <: SymbolLoaders.this.type }
  
  val platform: LoadersPlatform
}
 
trait Platform {
  type PlatformLoaders <: SymbolLoaders { type LoadersPlatform <: Platform.this.type }

  val loaders: PlatformLoaders
}

trait JavaPlatform extends Platform {
  type PlatformLoaders = loaders.type 

  object loaders extends SymbolLoaders {
    type LoadersPlatform = JavaPlatform.this.type  
    
    val platform: JavaPlatform.this.type = ???    
  }
}

doesn't compile with the cyclic error reported:

illegal cyclic reference involving type PlatformLoaders
  type PlatformLoaders = loaders.type

According to Adriaan, this should compile. We tried to debug it for a while and the cyclic error is thrown at Types.scala:3507:

    if (sym1.isAliasType && sameLength(sym1.info.typeParams, args) && !sym1.lockOK)
      throw new RecoverableCyclicReference(sym1)

because it calls to sym1.info.

I'm not sure if we'll have time to look more deeply into this. However, I can refer to this ticket from a code where I need to insert casts as work-arounds for this problem.

@scabug
Copy link
Author

scabug commented Jul 8, 2013

Imported From: https://issues.scala-lang.org/browse/SI-7642?orig=1
Reporter: @gkossakowski
Affected Versions: 2.11.0-M3

@scabug
Copy link
Author

scabug commented Jul 9, 2013

@paulp said:
That might be some kind of record for most confusing reproduction. PlatformLoaders! LoadersPlatform! SymbolLoaders! An unused SymbolLoader! LOAD ALL THE THINGS!

Somewhat reduced.

trait P { type A ; val v: A }
trait Q extends P { type A = v.type }
// ./a.scala:2: error: illegal cyclic reference involving type A
// trait Q extends P { type A = v.type }
//                              ^
// one error found

@scabug
Copy link
Author

scabug commented Jul 9, 2013

@gkossakowski said:
Thanks for looking into that. It's true that I missed SymbolLoader which is not used anywhere in this example.

Now, when it comes to reproduction I think yours is slightly different (too small). If you change JavaPlatform to look like this:

trait JavaPlatform extends Platform {
  type PlatformLoaders = loaders2.type 

  object loaders2 extends SymbolLoaders {
    type LoadersPlatform = JavaPlatform.this.type  
    
    val platform: JavaPlatform.this.type = ???    
  }
  val loaders: loaders2.type = loaders2
}

then the cyclic error disappears. All we did was an introduction of a dummy object loaders2 but the cycle is still there. If this typecheck then the original code should type check as well.

@scabug
Copy link
Author

scabug commented Jul 9, 2013

@paulp said:
Mine is different, yes, but it's not "too small". Unless you are saying mine should not typecheck for some reason, then there is no reason to chase something complicated around when something simple will do.

Mine is too small if you can fix mine and yours still doesn't typecheck.

@scabug
Copy link
Author

scabug commented Jul 22, 2013

@gkossakowski said:
What I meant by "too small" is that using structural types or abstract types won't get us that far. Compare this (which compiles):

class Foo {
  class C1 { val x: C2 }
  class C2 { val y: C1 }
}

with this (which doesn't compile):

class Foo {
  type T1 = { val x: T2 }
  type T2 = { val y: T1 }
}

I gave my original code another whirl. Here's a simplified sample (compared to what I originally reported) which compiles:

package test2

abstract class Component1 {  
  val comp2: Component2 {
    val comp1: Component1.this.type
  }
  def foo: String = "Hello, I'm Component1"
}

abstract class Component2 { 
  val comp1: Component1 {
    val comp2: Component2.this.type
  }
}

class Main {
  object comp1 extends Component1 {
    val comp2: Main.this.comp2.type = Main.this.comp2
  }
  object comp2 extends Component2 { 
    val comp1: Main.this.comp1.type = Main.this.comp1
  }
}

It compiles because we are using nominal types: object comp1 and object comp2. The problem is that I cannot override those objects so I cannot do something like:

class SubMain extends Main {
  override object comp1 extends Component1 {
    val comp2: SubMain.this.comp2.type = SubMain.this.comp2
    override def foo: String = "Hello, I'm overriden in SubMain"
  }
}

@scabug
Copy link
Author

scabug commented Jul 22, 2013

@paulp said:
FYI.

% scalac ./a.scala  -Yoverride-objects
%

@scabug
Copy link
Author

scabug commented Jul 22, 2013

@gkossakowski said:
If I turn SubMain into object I get:

scalac -Yoverride-objects a.scala
error: 
     while compiling: a.scala
        during phase: refchecks
     library version: version 2.10.2
    compiler version: version 2.10.2
  reconstructed args: -Yoverride-objects

  last tree to typer: This(object SubMain)
              symbol: object SubMain in package test2 (flags: <module>)
   symbol definition: class SubMain extends Main
                 tpe: test2.SubMain.type
       symbol owners: object SubMain -> package test2
      context owners: object comp1 -> object SubMain -> package test2

[...]

uncaught exception during compilation: java.lang.AssertionError
error: java.lang.AssertionError: assertion failed: List(value comp1, object comp1)
	at scala.reflect.internal.Symbols$Symbol.suchThat(Symbols.scala:1669)
	at scala.reflect.internal.Types$class.rebind(Types.scala:3529)
	at scala.reflect.internal.Types$class.singleType(Types.scala:3548)
	at scala.reflect.internal.SymbolTable.singleType(SymbolTable.scala:13)
	at scala.reflect.internal.Types$AsSeenFromMap.apply(Types.scala:4554)

@scabug
Copy link
Author

scabug commented Jul 22, 2013

@paulp said:
Oh, I didn't mean to say it was shovel-ready. That's why the -Y. But it is needed.

@scabug
Copy link
Author

scabug commented Jul 22, 2013

@gkossakowski said:
Yeah. However, one thing I was wondering is what's the type of overridden val corresponding to an object? In other words, if we override an object and we add a new methods in the overriding object are we going to be able to see them? If so, it means that we refine a type when we override (which is what I expect) but in a super class the type is already a singleton type (of the object) so there's not much you can do, right?

@scabug
Copy link
Author

scabug commented Jul 22, 2013

@paulp said:
You can still refine a singleton type. If I understand the question, the fact that this compiles answers it; otherwise maybe reformulate it.

trait A {
  val x: AnyRef
  def f: x.type
}
trait B extends A {
  override val x: String
  def g = f.length
}

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

No branches or pull requests

1 participant