From 7d1f78aa77624d393d9805fd85f0faa06b821458 Mon Sep 17 00:00:00 2001 From: Miles Sabin Date: Tue, 9 Apr 2019 16:51:06 +0100 Subject: [PATCH 1/3] Preserve TypeLambdas in wildApprox wildApprox should not replace TypeParamRefs which are bound by TypeLambdas which are contained in the type being approximated. Fixes #i6238. --- .../dotty/tools/dotc/typer/ProtoTypes.scala | 66 ++++++---- tests/pos/i6238.scala | 121 ++++++++++++++++++ tests/run/implicit-functors2.scala | 50 ++++++++ 3 files changed, 215 insertions(+), 22 deletions(-) create mode 100644 tests/pos/i6238.scala create mode 100644 tests/run/implicit-functors2.scala diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 7e6e801c357f..67e34e9c481f 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -579,29 +579,30 @@ object ProtoTypes { /** Approximate occurrences of parameter types and uninstantiated typevars * by wildcard types. */ - private def wildApprox(tp: Type, theMap: WildApproxMap, seen: Set[TypeParamRef])(implicit ctx: Context): Type = tp match { + private def wildApprox(tp: Type, theMap: WildApproxMap, seen: Set[TypeParamRef], internal: Set[TypeLambda])(implicit ctx: Context): Type = tp match { case tp: NamedType => // default case, inlined for speed val isPatternBoundTypeRef = tp.isInstanceOf[TypeRef] && tp.symbol.is(Flags.Case) && !tp.symbol.isClass if (isPatternBoundTypeRef) WildcardType(tp.underlying.bounds) else if (tp.symbol.isStatic || (tp.prefix `eq` NoPrefix)) tp - else tp.derivedSelect(wildApprox(tp.prefix, theMap, seen)) + else tp.derivedSelect(wildApprox(tp.prefix, theMap, seen, internal)) case tp @ AppliedType(tycon, args) => - wildApprox(tycon, theMap, seen) match { + wildApprox(tycon, theMap, seen, internal) match { case _: WildcardType => WildcardType // this ensures we get a * type case tycon1 => tp.derivedAppliedType(tycon1, - args.mapConserve(arg => wildApprox(arg, theMap, seen))) + args.mapConserve(arg => wildApprox(arg, theMap, seen, internal))) } case tp: RefinedType => // default case, inlined for speed tp.derivedRefinedType( - wildApprox(tp.parent, theMap, seen), + wildApprox(tp.parent, theMap, seen, internal), tp.refinedName, - wildApprox(tp.refinedInfo, theMap, seen)) + wildApprox(tp.refinedInfo, theMap, seen, internal)) case tp: AliasingBounds => // default case, inlined for speed - tp.derivedAlias(wildApprox(tp.alias, theMap, seen)) + tp.derivedAlias(wildApprox(tp.alias, theMap, seen, internal)) + case tp @ TypeParamRef(tl, _) if internal.contains(tl) => tp case tp @ TypeParamRef(poly, pnum) => def wildApproxBounds(bounds: TypeBounds) = if (seen.contains(tp)) WildcardType - else WildcardType(wildApprox(bounds, theMap, seen + tp).bounds) + else WildcardType(wildApprox(bounds, theMap, seen + tp, internal).bounds) def unconstrainedApprox = wildApproxBounds(poly.paramInfos(pnum)) def approxPoly = if (ctx.mode.is(Mode.TypevarsMissContext)) unconstrainedApprox @@ -609,17 +610,17 @@ object ProtoTypes { ctx.typerState.constraint.entry(tp) match { case bounds: TypeBounds => wildApproxBounds(bounds) case NoType => unconstrainedApprox - case inst => wildApprox(inst, theMap, seen) + case inst => wildApprox(inst, theMap, seen, internal) } approxPoly case TermParamRef(mt, pnum) => - WildcardType(TypeBounds.upper(wildApprox(mt.paramInfos(pnum), theMap, seen))) + WildcardType(TypeBounds.upper(wildApprox(mt.paramInfos(pnum), theMap, seen, internal))) case tp: TypeVar => - wildApprox(tp.underlying, theMap, seen) + wildApprox(tp.underlying, theMap, seen, internal) case tp: AndType => def approxAnd = { - val tp1a = wildApprox(tp.tp1, theMap, seen) - val tp2a = wildApprox(tp.tp2, theMap, seen) + val tp1a = wildApprox(tp.tp1, theMap, seen, internal) + val tp2a = wildApprox(tp.tp2, theMap, seen, internal) def wildBounds(tp: Type) = if (tp.isInstanceOf[WildcardType]) tp.bounds else TypeBounds.upper(tp) if (tp1a.isInstanceOf[WildcardType] || tp2a.isInstanceOf[WildcardType]) @@ -630,8 +631,8 @@ object ProtoTypes { approxAnd case tp: OrType => def approxOr = { - val tp1a = wildApprox(tp.tp1, theMap, seen) - val tp2a = wildApprox(tp.tp2, theMap, seen) + val tp1a = wildApprox(tp.tp1, theMap, seen, internal) + val tp2a = wildApprox(tp.tp2, theMap, seen, internal) if (tp1a.isInstanceOf[WildcardType] || tp2a.isInstanceOf[WildcardType]) WildcardType(tp1a.bounds | tp2a.bounds) else @@ -639,24 +640,45 @@ object ProtoTypes { } approxOr case tp: SelectionProto => - tp.derivedSelectionProto(tp.name, wildApprox(tp.memberProto, theMap, seen), NoViewsAllowed) + tp.derivedSelectionProto(tp.name, wildApprox(tp.memberProto, theMap, seen, internal), NoViewsAllowed) case tp: ViewProto => tp.derivedViewProto( - wildApprox(tp.argType, theMap, seen), - wildApprox(tp.resultType, theMap, seen)) + wildApprox(tp.argType, theMap, seen, internal), + wildApprox(tp.resultType, theMap, seen, internal)) case _: ThisType | _: BoundType => // default case, inlined for speed tp + case tl: TypeLambda => + val internal1 = internal + tl + val paramInfos = tl.paramInfos + val paramInfos1 = tl.paramInfos.mapConserve { + case tb @ TypeBounds(lo, hi) => + tb.derivedTypeBounds( + wildApprox(lo, theMap, seen, internal1), + wildApprox(hi, theMap, seen, internal1) + ) + } + val res = tl.resultType + val res1 = wildApprox(res, theMap, seen, internal1) + if ((res eq res1) && (paramInfos eq paramInfos1)) tl + else { + def substBounds(tl1: TypeLambda)(tb: TypeBounds): TypeBounds = + tb.derivedTypeBounds(tb.lo.subst(tl, tl1), tb.hi.subst(tl, tl1)) + tl match { + case _: HKTypeLambda => HKTypeLambda(tl.paramNames)(tl1 => paramInfos.map(substBounds(tl1)), tl1 => res1.subst(tl, tl1)) + case _: PolyType => PolyType(tl.paramNames)(tl1 => paramInfos.map(substBounds(tl1)), tl1 => res1.subst(tl, tl1)) + } + } case _ => - (if (theMap != null && seen.eq(theMap.seen)) theMap else new WildApproxMap(seen)) + (if (theMap != null && seen.eq(theMap.seen)) theMap else new WildApproxMap(seen, internal)) .mapOver(tp) } - final def wildApprox(tp: Type)(implicit ctx: Context): Type = wildApprox(tp, null, Set.empty) + final def wildApprox(tp: Type)(implicit ctx: Context): Type = wildApprox(tp, null, Set.empty, Set.empty) @sharable object AssignProto extends UncachedGroundType with MatchAlways - private[ProtoTypes] class WildApproxMap(val seen: Set[TypeParamRef])(implicit ctx: Context) extends TypeMap { - def apply(tp: Type): Type = wildApprox(tp, this, seen) + private[ProtoTypes] class WildApproxMap(val seen: Set[TypeParamRef], val internal: Set[TypeLambda])(implicit ctx: Context) extends TypeMap { + def apply(tp: Type): Type = wildApprox(tp, this, seen, internal) } /** Dummy tree to be used as an argument of a FunProto or ViewProto type */ diff --git a/tests/pos/i6238.scala b/tests/pos/i6238.scala new file mode 100644 index 000000000000..9cd6d56d95ed --- /dev/null +++ b/tests/pos/i6238.scala @@ -0,0 +1,121 @@ +object K1 { + class Foo[T] + + class Bar[F[_]] + object Bar { + implicit def barF[F[_]](implicit fooF: Foo[Bar[F]]): Bar[F] = null + } + + class A[T] + object A { + implicit def fooA[F[_[_]]](implicit barB: F[B]): Foo[F[A]] = null + } + + class B[T] + object B { + implicit def fooB[F[_[_]]]: Foo[F[B]] = null + } +} + +object K1U { + class Foo[T] + + class Bar[F[_ <: Int]] + object Bar { + implicit def barF[F[_ <: Int]](implicit fooF: Foo[Bar[F]]): Bar[F] = null + } + + class A[T <: Int] + object A { + implicit def fooA[F[_[_ <: Int]]](implicit barB: F[B]): Foo[F[A]] = null + } + + class B[T <: Int] + object B { + implicit def fooB[F[_[_ <: Int]]]: Foo[F[B]] = null + } +} + +object K1L { + class Foo[T] + + class Bar[F[_ >: Int]] + object Bar { + implicit def barF[F[_ >: Int]](implicit fooF: Foo[Bar[F]]): Bar[F] = null + } + + class A[T >: Int] + object A { + implicit def fooA[F[_[_ >: Int]]](implicit barB: F[B]): Foo[F[A]] = null + } + + class B[T >: Int] + object B { + implicit def fooB[F[_[_ >: Int]]]: Foo[F[B]] = null + } +} + +object K11 { + class Foo[T] + + class Bar[F[_[_]]] + object Bar { + implicit def barF[F[_[_]]](implicit fooF: Foo[Bar[F]]): Bar[F] = null + } + + class A[T[_]] + object A { + implicit def fooA[F[_[_[_]]]](implicit barB: F[B]): Foo[F[A]] = null + } + + class B[T[_]] + object B { + implicit def fooB[F[_[_[_]]]]: Foo[F[B]] = null + } +} + +object K2 { + class Foo[T] + + class Bar[F[_, _]] + object Bar { + implicit def barF[F[_, _]](implicit fooF: Foo[Bar[F]]): Bar[F] = null + } + + class A[T, U] + object A { + implicit def fooA[F[_[_, _]]](implicit barB: F[B]): Foo[F[A]] = null + } + + class B[T, U] + object B { + implicit def fooB[F[_[_, _]]]: Foo[F[B]] = null + } +} + +object Test { + { + import K1._ + implicitly[Bar[A]] + } + + { + import K1U._ + implicitly[Bar[A]] + } + + { + import K1L._ + implicitly[Bar[A]] + } + + { + import K11._ + implicitly[Bar[A]] + } + + { + import K2._ + implicitly[Bar[A]] + } +} diff --git a/tests/run/implicit-functors2.scala b/tests/run/implicit-functors2.scala new file mode 100644 index 000000000000..9a813253e9a3 --- /dev/null +++ b/tests/run/implicit-functors2.scala @@ -0,0 +1,50 @@ +object Utils { + type Id[t] = t + type Const[c] = [t] => c +} + +import Utils._ + +abstract class ErasedInstances { type FT } +class ErasedProductInstances(override val toString: String) extends ErasedInstances +class ErasedCoproductInstances(override val toString: String) extends ErasedInstances + +object K1 { + type Instances[F[_[_]], T[_]] = ErasedInstances { type FT = F[T] ; type C = F } +} + +class Functor[F[_]](override val toString: String) + +object Functor { + inline def apply[F[_]](implicit ff: Functor[F]): Functor[F] = ff + + implicit val functorId: Functor[Id] = new Functor("functorId") + + implicit def functorNested[F[_], G[_]](implicit ff: Functor[F], fg: Functor[G]): Functor[[t] => F[G[t]]] = new Functor(s"functorNested($ff, $fg)") + + implicit def functorGen[F[_]](implicit inst: K1.Instances[Functor, F]): Functor[F] = new Functor(s"functorGen($inst") + + implicit def functorConst[T]: Functor[Const[T]] = new Functor(s"functorConst") +} + +sealed trait Opt[+A] +object Opt { + implicit def optInstances[F[_[_]]](implicit fs: F[Sm], fn: F[[t] => Nn.type]): ErasedCoproductInstances { type FT = F[Opt] ; type C = F } = + new ErasedCoproductInstances(s"optInstances($fs, $fn)") { type FT = F[Opt] ; type C = F } +} + +case class Sm[+A](value: A) extends Opt[A] +object Sm { + implicit def smInstances[F[_[_]]](implicit fi: F[Id]): ErasedProductInstances { type FT = F[Sm] ; type C = F } = + new ErasedProductInstances(s"smInstances($fi)") { type FT = F[Sm] ; type C = F } +} + +case object Nn extends Opt[Nothing] + +object Test extends App { + assert(Functor[Const[Nn.type]].toString == "functorConst") + assert(Functor[Sm].toString == "functorGen(smInstances(functorId)") + assert(Functor[Opt].toString == "functorGen(optInstances(functorGen(smInstances(functorId), functorConst)") + assert(Functor[[t] => Opt[Opt[t]]].toString == "functorNested(functorGen(optInstances(functorGen(smInstances(functorId), functorConst), functorGen(optInstances(functorGen(smInstances(functorId), functorConst))") +} + From 85c641ff14b1d2f93c14269205b2441343ca0aff Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 10 Apr 2019 08:48:35 +0000 Subject: [PATCH 2/3] Simplifications - Use derivedLambdaType - Drop parameters of PolyTypes altogether --- .../dotty/tools/dotc/typer/ProtoTypes.scala | 27 +++++-------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 67e34e9c481f..3cf46348816e 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -647,27 +647,14 @@ object ProtoTypes { wildApprox(tp.resultType, theMap, seen, internal)) case _: ThisType | _: BoundType => // default case, inlined for speed tp - case tl: TypeLambda => + case tl: HKTypeLambda => val internal1 = internal + tl - val paramInfos = tl.paramInfos - val paramInfos1 = tl.paramInfos.mapConserve { - case tb @ TypeBounds(lo, hi) => - tb.derivedTypeBounds( - wildApprox(lo, theMap, seen, internal1), - wildApprox(hi, theMap, seen, internal1) - ) - } - val res = tl.resultType - val res1 = wildApprox(res, theMap, seen, internal1) - if ((res eq res1) && (paramInfos eq paramInfos1)) tl - else { - def substBounds(tl1: TypeLambda)(tb: TypeBounds): TypeBounds = - tb.derivedTypeBounds(tb.lo.subst(tl, tl1), tb.hi.subst(tl, tl1)) - tl match { - case _: HKTypeLambda => HKTypeLambda(tl.paramNames)(tl1 => paramInfos.map(substBounds(tl1)), tl1 => res1.subst(tl, tl1)) - case _: PolyType => PolyType(tl.paramNames)(tl1 => paramInfos.map(substBounds(tl1)), tl1 => res1.subst(tl, tl1)) - } - } + tl.derivedLambdaType( + paramInfos = tl.paramInfos.mapConserve(wildApprox(_, theMap, seen, internal1).bounds), + resType = wildApprox(tl.resType, theMap, seen, internal1) + ) + case tl: PolyType => + wildApprox(tl.resType, theMap, seen, internal) case _ => (if (theMap != null && seen.eq(theMap.seen)) theMap else new WildApproxMap(seen, internal)) .mapOver(tp) From 1a6ca25a009722a7af9d628c9c8d434d837cd289 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 10 Apr 2019 15:42:09 +0000 Subject: [PATCH 3/3] Drop special treatment of PolyTypes in wildApprox I think it could go either way, but I did not find an instance where we would actually call wildApprox on a PolyType. So it's better to simplify and treat PolyTypes just like other TypeLambdas. --- compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 3cf46348816e..d2736866121a 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -647,14 +647,12 @@ object ProtoTypes { wildApprox(tp.resultType, theMap, seen, internal)) case _: ThisType | _: BoundType => // default case, inlined for speed tp - case tl: HKTypeLambda => + case tl: TypeLambda => val internal1 = internal + tl tl.derivedLambdaType( paramInfos = tl.paramInfos.mapConserve(wildApprox(_, theMap, seen, internal1).bounds), resType = wildApprox(tl.resType, theMap, seen, internal1) ) - case tl: PolyType => - wildApprox(tl.resType, theMap, seen, internal) case _ => (if (theMap != null && seen.eq(theMap.seen)) theMap else new WildApproxMap(seen, internal)) .mapOver(tp)