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

Avoid bidirectional GADT typebounds from fullBounds #15683

Merged
merged 5 commits into from
Feb 16, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 27 additions & 2 deletions compiler/src/dotty/tools/dotc/core/GadtConstraint.scala
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,33 @@ class GadtConstraint private (
externalize(constraint.nonParamBounds(param)).bounds

def fullLowerBound(param: TypeParamRef)(using Context): Type =
constraint.minLower(param).foldLeft(nonParamBounds(param).lo) {
val self = externalize(param)
constraint.minLower(param).filterNot { p =>
dwijnand marked this conversation as resolved.
Show resolved Hide resolved
val sym = paramSymbol(p)
sym.name.is(NameKinds.UniqueName) && {
val hi = sym.info.hiBound
!hi.isExactlyAny && self <:< hi
dwijnand marked this conversation as resolved.
Show resolved Hide resolved
// drop any lower param that is a GADT symbol
// and is upper-bounded by a non-Any super-type of the original parameter
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm looks like we still need to update the comment after changing <:< to =:=

// e.g. in pos/i14287.min
// B$1 had info <: X and fullBounds >: B$2 <: X, and
// B$2 had info <: B$1 and fullBounds <: B$1
// We can use the info of B$2 to drop the lower-bound of B$1
// and return non-bidirectional bounds B$1 <: X and B$2 <: B$1.
}
}.foldLeft(nonParamBounds(param).lo) {
(t, u) => t | externalize(u)
}

def fullUpperBound(param: TypeParamRef)(using Context): Type =
constraint.minUpper(param).foldLeft(nonParamBounds(param).hi) { (t, u) =>
val self = externalize(param)
constraint.minUpper(param).filterNot { p =>
val sym = paramSymbol(p)
sym.name.is(NameKinds.UniqueName) && {
val lo = sym.info.loBound
!lo.isExactlyNothing && lo <:< self // same as fullLowerBounds
}
}.foldLeft(nonParamBounds(param).hi) { (t, u) =>
val eu = externalize(u)
// Any as the upper bound means "no bound", but if F is higher-kinded,
// Any & F = F[_]; this is wrong for us so we need to short-circuit
Expand All @@ -96,6 +117,10 @@ class GadtConstraint private (
def tvarOrError(sym: Symbol)(using Context): TypeVar =
mapping(sym).ensuring(_ != null, i"not a constrainable symbol: $sym").uncheckedNN

private def paramSymbol(p: TypeParamRef): Symbol = reverseMapping(p) match
case sym: Symbol => sym
case null => NoSymbol

@tailrec final def stripInternalTypeVar(tp: Type): Type = tp match
case tv: TypeVar =>
val inst = constraint.instType(tv)
Expand Down
7 changes: 7 additions & 0 deletions tests/pos/i14287.min.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
enum Foo[+F[_]]:
case Bar[B[_]](value: Foo[B]) extends Foo[B]

class Test:
def test[X[_]](foo: Foo[X]): Foo[X] = foo match
case Foo.Bar(Foo.Bar(x)) => Foo.Bar(x)
case _ => foo
16 changes: 16 additions & 0 deletions tests/pos/i14287.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// scalac: -Yno-deep-subtypes
enum Free[+F[_], A]:
case Return(a: A)
case Suspend(s: F[A])
case FlatMap[F[_], A, B](
s: Free[F, A],
f: A => Free[F, B]) extends Free[F, B]

def flatMap[F2[x] >: F[x], B](f: A => Free[F2,B]): Free[F2,B] =
FlatMap(this, f)

@scala.annotation.tailrec
final def step: Free[F, A] = this match
case FlatMap(FlatMap(fx, f), g) => fx.flatMap(x => f(x).flatMap(y => g(y))).step
case FlatMap(Return(x), f) => f(x).step
case _ => this
8 changes: 8 additions & 0 deletions tests/pos/i15523.avoid.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// scalac: -Werror
// like the original, but with a case body `a`
// which caused type avoidance to infinitely recurse
sealed trait Parent
final case class Leaf[A, B >: A](a: A, b: B) extends Parent

def run(x: Parent) = x match
case Leaf(a, _) => a
7 changes: 7 additions & 0 deletions tests/pos/i15523.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// scalac: -Werror
sealed trait Parent
final case class Leaf[A, B >: A](a: A, b: B) extends Parent

def run(x: Parent): Unit = x match {
case Leaf(a, b) =>
}
52 changes: 52 additions & 0 deletions tests/pos/i16777.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// scalac: -Ykind-projector:underscores

sealed abstract class Free[+S[_, _], +E, +A] {
@inline final def flatMap[S1[e, a] >: S[e, a], B, E1 >: E](fun: A => Free[S1, E1, B]): Free[S1, E1, B] = Free.FlatMapped[S1, E, E1, A, B](this, fun)
@inline final def map[B](fun: A => B): Free[S, E, B] = flatMap(a => Free.pure[S, B](fun(a)))
@inline final def as[B](as: => B): Free[S, E, B] = map(_ => as)
@inline final def *>[S1[e, a] >: S[e, a], B, E1 >: E](sc: Free[S1, E1, B]): Free[S1, E1, B] = flatMap(_ => sc)
@inline final def <*[S1[e, a] >: S[e, a], B, E1 >: E](sc: Free[S1, E1, B]): Free[S1, E1, A] = flatMap(r => sc.as(r))

@inline final def void: Free[S, E, Unit] = map(_ => ())

// FIXME: Scala 3.1.4 bug: false unexhaustive match warning
/// @nowarn("msg=pattern case: Free.FlatMapped")
@inline final def foldMap[S1[e, a] >: S[e, a], G[+_, +_]](transform: S1 ~>> G)(implicit G: Monad2[G]): G[E, A] = {
this match {
case Free.Pure(a) => G.pure(a)
case Free.Suspend(a) => transform.apply(a)
case Free.FlatMapped(sub, cont) =>
sub match {
case Free.FlatMapped(sub2, cont2) => sub2.flatMap(a => cont2(a).flatMap(cont)).foldMap(transform)
case another => G.flatMap(another.foldMap(transform))(cont(_).foldMap(transform))
}
}
}
}

trait ~>>[-F[_, _], +G[_, _]] {
def apply[E, A](f: F[E, A]): G[E, A]
}

object Free {
@inline def pure[S[_, _], A](a: A): Free[S, Nothing, A] = Pure(a)
@inline def lift[S[_, _], E, A](s: S[E, A]): Free[S, E, A] = Suspend(s)

final case class Pure[S[_, _], A](a: A) extends Free[S, Nothing, A] {
override def toString: String = s"Pure:[$a]"
}
final case class Suspend[S[_, _], E, A](a: S[E, A]) extends Free[S, E, A] {
override def toString: String = s"Suspend:[$a]"
}
final case class FlatMapped[S[_, _], E, E1 >: E, A, B](sub: Free[S, E, A], cont: A => Free[S, E1, B]) extends Free[S, E1, B] {
override def toString: String = s"FlatMapped:[sub=$sub]"
}
}

type Monad2[F[+_, +_]] = Monad3[λ[(`-R`, `+E`, `+A`) => F[E, A]]]

trait Monad3[F[-_, +_, +_]] {
def flatMap[R, E, A, B](r: F[R, E, A])(f: A => F[R, E, B]): F[R, E, B]
def flatten[R, E, A](r: F[R, E, F[R, E, A]]): F[R, E, A] = flatMap(r)(identity)
def pure[A](a: A): F[Any, Nothing, A]
}