Skip to content

Incorrect code ends up passing the typechecker due to GADT logic misfiring #9274

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

Closed
djspiewak opened this issue Jul 2, 2020 · 6 comments · Fixed by #9322
Closed

Incorrect code ends up passing the typechecker due to GADT logic misfiring #9274

djspiewak opened this issue Jul 2, 2020 · 6 comments · Fixed by #9322
Assignees
Milestone

Comments

@djspiewak
Copy link

Minimized code

This could probably be minimized further.

import cats.Order
import cats.implicits._

def test[F[_], B](lhs: F[B], tolerance: B)(implicit ord: Order[F[B]]): Boolean =
  (lhs: F[B]) <= (tolerance: B)

Cats 2.1.1 compiled for Scala 2.13.

Output

Compiles and results in a ClassCastException.

Expectation

Shouldn't compile. The ascriptions aren't necessary to trigger the bug, I just included them for clarity.

This may be specific to the Scala 2 unpickler. I wasn't able to reproduce it with this pure-Dotty minimization:

object Foo {

  implicit def toSyntax[A](a: A): Syntax[A] =
    new Syntax[A](a)

  class Syntax[A](self: A) {
    def <=(other: A)(implicit A: Order[A]): Boolean = true
  }

  def test[F[_], B](lhs: F[B], tolerance: B)(implicit ord: Order[F[B]]): Boolean =
    (lhs: F[B]) <= (tolerance: B)
}

The above fails to compile, as I would expect.

@smarter
Copy link
Member

smarter commented Jul 2, 2020

[log typer]           ==> adapting cats.implicits.catsSyntaxPartialOrder[F[B]](lhs:F[B])(ord) to ?{ <= : [applied to (tolerance:B) returning ?] } 
?
adapt to subtype cats.syntax.PartialOrderOps[F[B]] !<:< ?{ <= : [applied to (tolerance:B) returning ?] }
approx F(param)1, from below = false, bound = [_$1] =>> Any, inst = [_$1] =>> Any
approx B(param)1, from below = false, bound = Any, inst = Any
[log typer]           <== adapting cats.implicits.catsSyntaxPartialOrder[F[B]](lhs:F[B])(ord) to ?{ <= : [applied to (tolerance:B) returning ?] } 
 = cats.implicits.catsSyntaxPartialOrder[F[B]](lhs:F[B])(ord):
  cats.syntax.PartialOrderOps[Any]

This is very weird, we have a value of type cats.syntax.PartialOrderOps[F[B]], then somehow we end up instantiating F to [X] => Any and B to Any which doesn't make sense since they're type parameters of the enclosing method, and from that we get an ascription cats.syntax.PartialOrderOps[Any] which is unsound since PartialOrderOps is invariant.

@smarter
Copy link
Member

smarter commented Jul 2, 2020

This is somehow caused by the GADT logic, if I comment out the following lines: https://github.com/lampepfl/dotty/blob/8a4c442acd67591c672a80d0fd865fbc5cc110c5/compiler/src/dotty/tools/dotc/typer/Typer.scala#L3377-L3397
then typechecking fails as expected.

I don't know how to reproduce this without cats either, but it's not too difficult to try out from the dotty repo:

@smarter smarter added this to the 0.26.0-RC1 milestone Jul 2, 2020
@smarter smarter changed the title Incorrect typecheck in 2.13 extension method call-site Incorrect typecheck in 2.13 extension method call-site due to GADT logic misfiring Jul 2, 2020
@smarter smarter changed the title Incorrect typecheck in 2.13 extension method call-site due to GADT logic misfiring Incorrect code ends up passing the typechecker due to GADT logic misfiring Jul 2, 2020
@smarter
Copy link
Member

smarter commented Jul 2, 2020

What I gather is happening:

@smarter
Copy link
Member

smarter commented Jul 2, 2020

/cc @radeusgd since ApproximateGadtAccumulator was added in #8728

@abgruszecki
Copy link
Contributor

@smarter I've been suspecting that I cut a bit too many shortcuts when writing ApproximateGadtAccumulator, I'll take a look at this one.

@abgruszecki
Copy link
Contributor

Oh, and before I forget to write this here for clarity/posterity: adding type parameters of every function we see to GadtConstraint is precisely how it is intended to work. When actually inferring constraints, we need to know whether a symbol is constrainable or not; all type parameters of enclosing functions are intended to be constrainable; it's not possible to tell whether a symbol is a type parameter of an enclosing function after descending into its body; therefore, we need to stick those symbols somewhere when we see them and we stick them into GadtConstraint, which essentially functions like a scope for GADT constraints.

Corrections to the above welcome.

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.

3 participants