Skip to content

Commit 5817920

Browse files
authored
Avoid bidirectional GADT typebounds from fullBounds (#15683)
2 parents 221bae8 + 37aeb3d commit 5817920

File tree

6 files changed

+111
-7
lines changed

6 files changed

+111
-7
lines changed

compiler/src/dotty/tools/dotc/core/GadtConstraint.scala

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package dotc
33
package core
44

55
import Contexts.*, Decorators.*, Symbols.*, Types.*
6+
import NameKinds.UniqueName
67
import config.Printers.{gadts, gadtsConstr}
78
import util.{SimpleIdentitySet, SimpleIdentityMap}
89
import printing._
@@ -71,16 +72,29 @@ class GadtConstraint private (
7172
externalize(constraint.nonParamBounds(param)).bounds
7273

7374
def fullLowerBound(param: TypeParamRef)(using Context): Type =
74-
constraint.minLower(param).foldLeft(nonParamBounds(param).lo) {
75-
(t, u) => t | externalize(u)
75+
val self = externalize(param)
76+
constraint.minLower(param).foldLeft(nonParamBounds(param).lo) { (acc, p) =>
77+
externalize(p) match
78+
// drop any lower param that is a GADT symbol
79+
// and is upper-bounded by a non-Any super-type of the original parameter
80+
// e.g. in pos/i14287.min
81+
// B$1 had info <: X and fullBounds >: B$2 <: X, and
82+
// B$2 had info <: B$1 and fullBounds <: B$1
83+
// We can use the info of B$2 to drop the lower-bound of B$1
84+
// and return non-bidirectional bounds B$1 <: X and B$2 <: B$1.
85+
case tp: TypeRef if tp.symbol.isPatternBound && self =:= tp.info.hiBound => acc
86+
case tp => acc | tp
7687
}
7788

7889
def fullUpperBound(param: TypeParamRef)(using Context): Type =
79-
constraint.minUpper(param).foldLeft(nonParamBounds(param).hi) { (t, u) =>
80-
val eu = externalize(u)
81-
// Any as the upper bound means "no bound", but if F is higher-kinded,
82-
// Any & F = F[_]; this is wrong for us so we need to short-circuit
83-
if t.isAny then eu else t & eu
90+
val self = externalize(param)
91+
constraint.minUpper(param).foldLeft(nonParamBounds(param).hi) { (acc, u) =>
92+
externalize(u) match
93+
case tp: TypeRef if tp.symbol.isPatternBound && self =:= tp.info.loBound => acc // like fullLowerBound
94+
case tp =>
95+
// Any as the upper bound means "no bound", but if F is higher-kinded,
96+
// Any & F = F[_]; this is wrong for us so we need to short-circuit
97+
if acc.isAny then tp else acc & tp
8498
}
8599

86100
def externalize(tp: Type, theMap: TypeMap | Null = null)(using Context): Type = tp match

tests/pos/i14287.min.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
enum Foo[+F[_]]:
2+
case Bar[B[_]](value: Foo[B]) extends Foo[B]
3+
4+
class Test:
5+
def test[X[_]](foo: Foo[X]): Foo[X] = foo match
6+
case Foo.Bar(Foo.Bar(x)) => Foo.Bar(x)
7+
case _ => foo

tests/pos/i14287.scala

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// scalac: -Yno-deep-subtypes
2+
enum Free[+F[_], A]:
3+
case Return(a: A)
4+
case Suspend(s: F[A])
5+
case FlatMap[F[_], A, B](
6+
s: Free[F, A],
7+
f: A => Free[F, B]) extends Free[F, B]
8+
9+
def flatMap[F2[x] >: F[x], B](f: A => Free[F2,B]): Free[F2,B] =
10+
FlatMap(this, f)
11+
12+
@scala.annotation.tailrec
13+
final def step: Free[F, A] = this match
14+
case FlatMap(FlatMap(fx, f), g) => fx.flatMap(x => f(x).flatMap(y => g(y))).step
15+
case FlatMap(Return(x), f) => f(x).step
16+
case _ => this

tests/pos/i15523.avoid.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// scalac: -Werror
2+
// like the original, but with a case body `a`
3+
// which caused type avoidance to infinitely recurse
4+
sealed trait Parent
5+
final case class Leaf[A, B >: A](a: A, b: B) extends Parent
6+
7+
def run(x: Parent) = x match
8+
case Leaf(a, _) => a

tests/pos/i15523.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// scalac: -Werror
2+
sealed trait Parent
3+
final case class Leaf[A, B >: A](a: A, b: B) extends Parent
4+
5+
def run(x: Parent): Unit = x match {
6+
case Leaf(a, b) =>
7+
}

tests/pos/i16777.scala

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// scalac: -Ykind-projector:underscores
2+
3+
sealed abstract class Free[+S[_, _], +E, +A] {
4+
@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)
5+
@inline final def map[B](fun: A => B): Free[S, E, B] = flatMap(a => Free.pure[S, B](fun(a)))
6+
@inline final def as[B](as: => B): Free[S, E, B] = map(_ => as)
7+
@inline final def *>[S1[e, a] >: S[e, a], B, E1 >: E](sc: Free[S1, E1, B]): Free[S1, E1, B] = flatMap(_ => sc)
8+
@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))
9+
10+
@inline final def void: Free[S, E, Unit] = map(_ => ())
11+
12+
// FIXME: Scala 3.1.4 bug: false unexhaustive match warning
13+
/// @nowarn("msg=pattern case: Free.FlatMapped")
14+
@inline final def foldMap[S1[e, a] >: S[e, a], G[+_, +_]](transform: S1 ~>> G)(implicit G: Monad2[G]): G[E, A] = {
15+
this match {
16+
case Free.Pure(a) => G.pure(a)
17+
case Free.Suspend(a) => transform.apply(a)
18+
case Free.FlatMapped(sub, cont) =>
19+
sub match {
20+
case Free.FlatMapped(sub2, cont2) => sub2.flatMap(a => cont2(a).flatMap(cont)).foldMap(transform)
21+
case another => G.flatMap(another.foldMap(transform))(cont(_).foldMap(transform))
22+
}
23+
}
24+
}
25+
}
26+
27+
trait ~>>[-F[_, _], +G[_, _]] {
28+
def apply[E, A](f: F[E, A]): G[E, A]
29+
}
30+
31+
object Free {
32+
@inline def pure[S[_, _], A](a: A): Free[S, Nothing, A] = Pure(a)
33+
@inline def lift[S[_, _], E, A](s: S[E, A]): Free[S, E, A] = Suspend(s)
34+
35+
final case class Pure[S[_, _], A](a: A) extends Free[S, Nothing, A] {
36+
override def toString: String = s"Pure:[$a]"
37+
}
38+
final case class Suspend[S[_, _], E, A](a: S[E, A]) extends Free[S, E, A] {
39+
override def toString: String = s"Suspend:[$a]"
40+
}
41+
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] {
42+
override def toString: String = s"FlatMapped:[sub=$sub]"
43+
}
44+
}
45+
46+
type Monad2[F[+_, +_]] = Monad3[λ[(`-R`, `+E`, `+A`) => F[E, A]]]
47+
48+
trait Monad3[F[-_, +_, +_]] {
49+
def flatMap[R, E, A, B](r: F[R, E, A])(f: A => F[R, E, B]): F[R, E, B]
50+
def flatten[R, E, A](r: F[R, E, F[R, E, A]]): F[R, E, A] = flatMap(r)(identity)
51+
def pure[A](a: A): F[Any, Nothing, A]
52+
}

0 commit comments

Comments
 (0)