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

Recursive implicit view should yield lint warning #8357

Closed
scabug opened this issue Mar 2, 2014 · 11 comments · Fixed by scala/scala#9019
Closed

Recursive implicit view should yield lint warning #8357

scabug opened this issue Mar 2, 2014 · 11 comments · Fixed by scala/scala#9019

Comments

@scabug
Copy link

scabug commented Mar 2, 2014

Apparently structural types and implicit conversions don't play along very well; see the following repro (directly runnable on the Scala console):

scala> type Structural = { def size: Int }
defined type alias Structural

scala> import scala.collection.GenTraversableOnce
import scala.collection.GenTraversableOnce

scala> implicit def genericTraversableOnce2HasSize[ T ]( gto: T )( implicit ev: T => GenTraversableOnce[_] ): Structural = ev( gto )
warning: there were 1 feature warning(s); re-run with -feature for details
genericTraversableOnce2HasSize: [T](gto: T)(implicit ev: T => scala.collection.GenTraversableOnce[_])Structural

scala> def test( v: Structural ) { println( v.size ) }
warning: there were 1 feature warning(s); re-run with -feature for details
test: (v: Structural)Unit

scala> test( Some( 1 ) )
java.lang.StackOverflowError
	at .genericTraversableOnce2HasSize(<console>:9)
	at .genericTraversableOnce2HasSize(<console>:9)
	at .genericTraversableOnce2HasSize(<console>:9)
	at .genericTraversableOnce2HasSize(<console>:9)
	at .genericTraversableOnce2HasSize(<console>:9)
	at .genericTraversableOnce2HasSize(<console>:9)
	at .genericTraversableOnce2HasSize(<console>:9)
	at .genericTraversableOnce2HasSize(<console>:9)
	at .genericTraversableOnce2HasSize(<console>:9)
	at .genericTraversableOnce2HasSize(<console>:9)

I couldn't tell you why, but this works around the problem:

scala> implicit def genericTraversableOnce2HasSize[ T ]( gto: T )( implicit ev: T => GenTraversableOnce[_] ): Structural =
     |   if ( ev == conforms ) gto.asInstanceOf[ Structural ] else ev( gto )
warning: there were 1 feature warning(s); re-run with -feature for details
genericTraversableOnce2HasSize: [T](gto: T)(implicit ev: T => scala.collection.GenTraversableOnce[_])Structural

scala> test( Some( 1 ) )
1

This makes no sense though, because the conforms call should result in exactly the same behavior. Any help would be appreciated...

@scabug
Copy link
Author

scabug commented Mar 2, 2014

Imported From: https://issues.scala-lang.org/browse/SI-8357?orig=1
Reporter: Tomer Gabel (holograph)
Affected Versions: 2.10.3, 2.11.0-RC1

@scabug
Copy link
Author

scabug commented Mar 3, 2014

@adriaanm said (edited on Mar 3, 2014 9:31:16 PM UTC):
It's not related to structural types, nor existentials.

This will also loop:

trait Foo
trait Sizeable { def size: Int }
implicit def fooHasSize[T](gto: T)(implicit ev: T => Foo): Sizeable = ev(gto)
fooHasSize(new Foo{})

-Xprint:typer reveals why:

implicit def fooHasSize[T](gto: T)(implicit ev: T => Foo): Sizeable 
 = fooHasSize[Foo](ev.apply(gto))(Predef.$conforms[Foo])

@scabug
Copy link
Author

scabug commented Mar 3, 2014

Tomer Gabel (holograph) said (edited on Mar 3, 2014 7:58:12 PM UTC):
Foo[Any] is not a Sizeable, per your example, why would this example even compile?
Update Well OK, it does compile, but I must be missing something basic because I still don't get why this compiles.

@scabug
Copy link
Author

scabug commented Mar 3, 2014

@retronym said (edited on Mar 3, 2014 8:24:45 PM UTC):
GenTraversableOnce is now a universal trait, meaning it now extends Any rather than AnyRef can be a parent of a value class.

This has a unintended interaction with structural types:

trait T extends Any {
  def size: Int
}
object Test {
  type Structural = { def size: Int }
  def foo(x: T): Structural = x
}
 found   : x.type (with underlying type T)
 required: Test.Structural
    (which expands to)  AnyRef{def size: Int}
  def foo(x: T): Structural = x
                              ^

Because of this, your the result of ev(gto) is itself adapted with the very same implicit view that it defines. (We need a lint rule for that!)

    // -Xprint:typer
    implicit def hasSize[T](gto: T)(implicit ev: T => scala.collection.GenTraversableOnce[_]): Test.Structural = {
      val temp: scala.collection.GenTraversableOnce[Any] = ev.apply(gto);
      Test.this.hasSize[scala.collection.GenTraversableOnce[Any]](temp)(scala.this.Predef.$conforms[scala.collection.GenTraversableOnce[Any]])
    };

Workaround:

type Structural = Any { def size: Int }

(The "naked" {def size: Int} is a shorthand for AnyRef { def size: Int})

@scabug
Copy link
Author

scabug commented Mar 3, 2014

Tomer Gabel (holograph) said:
Well, that I hadn't considered. So if I understand correctly, this should not compile at all, but rather emit an error saying that GenTraversableOnce[_] cannot conform to Test.Structural because it is not provably a reference type, whereas the lint rule you mention would only act as a safety net for similarly incorrectly-handled edge cases?

@scabug
Copy link
Author

scabug commented Mar 3, 2014

@retronym said:
It doesn't conform, but it can be adapted with your view.

@scabug
Copy link
Author

scabug commented Mar 3, 2014

Tomer Gabel (holograph) said:
Ah, I believe I fully understand now. Off the top of my head I can't think of a single case where a recursive resolution makes sense either.

@scabug
Copy link
Author

scabug commented Mar 3, 2014

@adriaanm said:
It's not something you can spec, because it's undecidable to detect infinite recursions in Scala.
A lint warning would be useful, whence the component this bug is in.

@scabug
Copy link
Author

scabug commented Mar 3, 2014

@adriaanm said:
Note that my earlier example is the same thing as what you reported, but it doesn't use structural types, existentials or universal traits.

@scabug
Copy link
Author

scabug commented Mar 6, 2014

Meir Maor (meirmaor) said:
Considering the comments above, a cleaner workaround would be writing "AnyRef with GenTraversableOnce[_] "

implicit def genericTraversableOnce2HasSize[ T ]( gto: T )( implicit ev: T => AnyRef with GenTraversableOnce[_] ): Structural = ev( gto )

@som-snytt
Copy link

-Wself-implicit by scala/scala#6318, upgraded by linked PR to -Xlint:implicit-recurses.

The temptation is to upgrade the message to: Curses! Implicit recurses!

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

Successfully merging a pull request may close this issue.

4 participants