diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 7e7132307b0e..64bf32c49f91 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -64,19 +64,8 @@ object ProtoTypes { i"""normalizedCompatible for $poly, $pt = $result |constraint was: ${ctx.typerState.constraint} |constraint now: ${newctx.typerState.constraint}""") - if result - && (ctx.typerState.constraint ne newctx.typerState.constraint) - && { - val existingVars = ctx.typerState.uninstVars.toSet - newctx.typerState.uninstVars.forall(existingVars.contains) - } - then newctx.typerState.commit() - // If the new constrait contains fresh type variables we cannot keep it, - // since those type variables are not instantiated anywhere in the source. - // See pos/i6682a.scala for a test case. See pos/11243.scala and pos/i5773b.scala - // for tests where it matters that we keep the constraint otherwise. - // TODO: A better solution would clean the new constraint, so that it "avoids" - // the problematic type variables. But we have not implemented such an algorithm yet. + if result && (ctx.typerState.constraint ne newctx.typerState.constraint) then + newctx.typerState.commit() result case _ => testCompat else explore(testCompat) diff --git a/tests/pos/i12126.scala b/tests/pos/i12126.scala new file mode 100644 index 000000000000..cffa7fbcbbca --- /dev/null +++ b/tests/pos/i12126.scala @@ -0,0 +1,59 @@ +object Structures: + + trait Functor[F[_]]: + extension [A](fa: F[A]) + def map[B](f: A => B): F[B] + def as[B](b: B): F[B] = map(_ => b) + def void: F[Unit] = as(()) + + trait Applicative[F[_]] extends Functor[F]: + def pure[A](a: A): F[A] + def unit: F[Unit] = pure(()) + extension[A](fa: F[A]) + def map2[B, C](fb: F[B], f: (A, B) => C): F[C] + def map[B](f: A => B): F[B] = + fa.map2(unit, (a, _) => f(a)) + + trait Monad[F[_]] extends Applicative[F]: + extension[A](fa: F[A]) + def flatMap[B](f: A => F[B]): F[B] + override def map[B](f: A => B): F[B] = + flatMap(a => pure(f(a))) + def map2[B, C](fb: F[B], f: (A, B) => C): F[C] = + flatMap(a => fb.map(b => f(a, b))) + + given Monad[List] with + def pure[A](a: A) = List(a) + extension[A](fa: List[A]) + def flatMap[B](f: A => List[B]) = fa.flatMap(f) + + given Monad[Option] with + def pure[A](a: A) = Some(a) + extension[A](fa: Option[A]) + def flatMap[B](f: A => Option[B]) = fa.flatMap(f) + + + opaque type Kleisli[F[_], A, B] = A => F[B] + + extension [F[_], A, B](k: Kleisli[F, A, B]) + def apply(a: A): F[B] = k(a) + + object Kleisli: + def apply[F[_], A, B](f: A => F[B]): Kleisli[F, A, B] = f + + given [F[_], A](using F: Monad[F]): Monad[[B] =>> Kleisli[F, A, B]] with + def pure[B](b: B) = Kleisli(_ => F.pure(b)) + extension[B](k: Kleisli[F, A, B]) + def flatMap[C](f: B => Kleisli[F, A, C]) = + a => k(a).flatMap(b => f(b)(a)) + +end Structures + +@main def run = + import Structures.{*, given} + println(List(1, 2, 3).map2(List(4, 5, 6), (_, _))) + + val p: Kleisli[Option, Int, Int] = Kleisli((x: Int) => if x % 2 == 0 then Some(x) else None) + val q: Kleisli[Option, Int, Int] = summon[Applicative[[B] =>> Kleisli[Option, Int, B]]].pure(20) + println(p.map2(q, _ + _)(42)) +