From c8eb14f8bb4141e9fc8de471990138c7b2617b03 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 6 May 2019 18:49:27 +0200 Subject: [PATCH 1/6] Replace singleton bounds before doing implicit search Before searching for implicit arguments, if a the constraint contains a type parameter ``` P >: A <: B ``` where `P` occurs in the searched-for type and `A =:= B`, change the constraint to ``` P := B ``` instead. This improves the implicit search by making the searched-for type have fewer uninstantiated type variables. --- .../dotty/tools/dotc/typer/Inferencing.scala | 25 +++++++++++++++++++ .../src/dotty/tools/dotc/typer/Typer.scala | 6 ++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index 0902964edea7..168a60db0ded 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -138,6 +138,31 @@ object Inferencing { } } + /** For all type parameters occurring in `tp`: + * If the bounds of `tp` in the current constraint are equal wrt =:=, + * instantiate the type parameter to the lower bound's approximation + * (approximation because of possible F-bounds). + */ + def replaceSingletons(tp: Type)(implicit ctx: Context): Unit = { + val tr = new TypeTraverser { + def traverse(tp: Type): Unit = { + tp match { + case param: TypeParamRef => + val constraint = ctx.typerState.constraint + if (constraint.contains(param) && + (ctx.typerComparer.fullUpperBound(param) frozen_<:< ctx.typecomparer.fullLowerBound(param))) { + typr.println(i"replace singleton $param := ${ctx.typeComparer.fullLowerBound(param)}") + ctx.typerState.constraint = constraint.replace(param, + ctx.typeComparer.approximation(param, fromBelow = true)) + } + case _ => + } + traverseChildren(tp) + } + } + tr.traverse(tp) + } + /** If `tree` has a type lambda type, infer its type parameters by comparing with expected type `pt` */ def inferTypeParams(tree: Tree, pt: Type)(implicit ctx: Context): Tree = tree.tpe match { case tl: TypeLambda => diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 6d1587ffc03d..d7b836923471 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2607,7 +2607,11 @@ class Typer extends Namer def adaptNoArgsImplicitMethod(wtp: MethodType): Tree = { assert(wtp.isImplicitMethod) val tvarsToInstantiate = tvarsInParams(tree, locked).distinct - wtp.paramInfos.foreach(instantiateSelected(_, tvarsToInstantiate)) + def instantiate(tp: Type): Unit = { + instantiateSelected(tp, tvarsToInstantiate) + replaceSingletons(tp) + } + wtp.paramInfos.foreach(instantiate) val constr = ctx.typerState.constraint def dummyArg(tp: Type) = untpd.Ident(nme.???).withTypeUnchecked(tp) From 18ce5d2aaa1a492db0f1557ebe62d0943b90f8c9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 6 May 2019 19:17:09 +0200 Subject: [PATCH 2/6] Don't instantiate hk type constructors too early The issue #6385 contains more explanations. --- .../tools/dotc/core/ConstraintHandling.scala | 10 ---------- .../dotty/tools/dotc/core/TypeComparer.scala | 6 +++--- compiler/test/dotc/pos-test-pickling.blacklist | 1 + .../dotty/tools/dotc/CompilationTests.scala | 1 + tests/{neg => neg-custom-args}/i1650.scala | 0 tests/neg/i6385a.scala | 11 +++++++++++ tests/{pos => pos-deep-subtype}/i6119.scala | 0 tests/pos/i6385.scala | 17 +++++++++++++++++ 8 files changed, 33 insertions(+), 13 deletions(-) rename tests/{neg => neg-custom-args}/i1650.scala (100%) create mode 100644 tests/neg/i6385a.scala rename tests/{pos => pos-deep-subtype}/i6119.scala (100%) create mode 100644 tests/pos/i6385.scala diff --git a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala index 4afd55efdefb..1e9a8c5e5816 100644 --- a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -520,16 +520,6 @@ trait ConstraintHandling[AbstractContext] { } } - /** Instantiate `param` to `tp` if the constraint stays satisfiable */ - protected def tryInstantiate(param: TypeParamRef, tp: Type)(implicit actx: AbstractContext): Boolean = { - val saved = constraint - constraint = - if (addConstraint(param, tp, fromBelow = true) && - addConstraint(param, tp, fromBelow = false)) constraint.replace(param, tp) - else saved - constraint ne saved - } - /** Check that constraint is fully propagated. See comment in Config.checkConstraintsPropagated */ def checkPropagated(msg: => String)(result: Boolean)(implicit actx: AbstractContext): Boolean = { if (Config.checkConstraintsPropagated && result && addConstraintInvocations == 0) { diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index c2ac412132c8..b0a6bf25b9d8 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -818,7 +818,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] { tycon1.dealiasKeepRefiningAnnots match { case tycon1: TypeParamRef => (tycon1 == tycon2 || - canConstrain(tycon1) && tryInstantiate(tycon1, tycon2)) && + canConstrain(tycon1) && isSubType(tycon1, tycon2)) && isSubArgs(args1, args2, tp1, tparams) case tycon1: TypeRef => tycon2.dealiasKeepRefiningAnnots match { @@ -892,7 +892,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] { tl => tparams1.map(tparam => tl.integrate(tparams, tparam.paramInfo).bounds), tl => tp1base.tycon.appliedTo(args1.take(lengthDiff) ++ tparams1.indices.toList.map(tl.paramRefs(_)))) - (assumedTrue(tycon2) || tryInstantiate(tycon2, tycon1.ensureLambdaSub)) && + (assumedTrue(tycon2) || isSubType(tycon1.ensureLambdaSub, tycon2)) && recur(tp1, tycon1.appliedTo(args2)) } } @@ -977,7 +977,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] { case param1: TypeParamRef => def canInstantiate = tp2 match { case AppliedType(tycon2, args2) => - tryInstantiate(param1, tycon2.ensureLambdaSub) && isSubArgs(args1, args2, tp1, tycon2.typeParams) + isSubType(param1, tycon2.ensureLambdaSub) && isSubArgs(args1, args2, tp1, tycon2.typeParams) case _ => false } diff --git a/compiler/test/dotc/pos-test-pickling.blacklist b/compiler/test/dotc/pos-test-pickling.blacklist index ac74bde3ed1b..19865bccd1ed 100644 --- a/compiler/test/dotc/pos-test-pickling.blacklist +++ b/compiler/test/dotc/pos-test-pickling.blacklist @@ -1,3 +1,4 @@ +i94-nada.scala i1812.scala i1867.scala i3067.scala diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index ce32f13c2892..1eb150c655f5 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -164,6 +164,7 @@ class CompilationTests extends ParallelTesting { compileFile("tests/neg-custom-args/nopredef.scala", defaultOptions.and("-Yno-predef")), compileFile("tests/neg-custom-args/noimports.scala", defaultOptions.and("-Yno-imports")), compileFile("tests/neg-custom-args/noimports2.scala", defaultOptions.and("-Yno-imports")), + compileFile("tests/neg-custom-args/i1650.scala", allowDeepSubtypes), compileFile("tests/neg-custom-args/i3882.scala", allowDeepSubtypes), compileFile("tests/neg-custom-args/i4372.scala", allowDeepSubtypes), compileFile("tests/neg-custom-args/i1754.scala", allowDeepSubtypes), diff --git a/tests/neg/i1650.scala b/tests/neg-custom-args/i1650.scala similarity index 100% rename from tests/neg/i1650.scala rename to tests/neg-custom-args/i1650.scala diff --git a/tests/neg/i6385a.scala b/tests/neg/i6385a.scala new file mode 100644 index 000000000000..17761cd076ed --- /dev/null +++ b/tests/neg/i6385a.scala @@ -0,0 +1,11 @@ +class Box[F[_]] + +class C[X] +class D[X] extends C[String] + +object Test { + def f[F[_]](x: Box[F]) = ??? + def db: Box[D] = ??? + def cb: Box[C] = db // error + f[[X] => C[X]](db) // error +} \ No newline at end of file diff --git a/tests/pos/i6119.scala b/tests/pos-deep-subtype/i6119.scala similarity index 100% rename from tests/pos/i6119.scala rename to tests/pos-deep-subtype/i6119.scala diff --git a/tests/pos/i6385.scala b/tests/pos/i6385.scala new file mode 100644 index 000000000000..8174a68dc253 --- /dev/null +++ b/tests/pos/i6385.scala @@ -0,0 +1,17 @@ +trait Tc1[A] +trait Tc2[A] extends Tc1[A] + +class PinTypeTo[K[_]] +object PinTypeTo { + implicit val pinType: PinTypeTo[Tc2] = new PinTypeTo[Tc2] +} + +class X +object X { + implicit def Tc2Instance[F[x] >: Tc2[x]: PinTypeTo]: F[X] = new Tc2[X] {} +} + +object app extends App { + implicitly[Tc2[X]] // ok + implicitly[Tc1[X]]//(X.Tc2Instance[Tc2]) // fails +} \ No newline at end of file From baaa562b2df5838b21c0ed8b424c86334ebd757c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 7 May 2019 08:53:17 +0200 Subject: [PATCH 3/6] Optimize replaceSingletons Avoid formation of full bounds. --- .../src/dotty/tools/dotc/typer/Inferencing.scala | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index 168a60db0ded..010fa798ade4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -149,11 +149,16 @@ object Inferencing { tp match { case param: TypeParamRef => val constraint = ctx.typerState.constraint - if (constraint.contains(param) && - (ctx.typerComparer.fullUpperBound(param) frozen_<:< ctx.typecomparer.fullLowerBound(param))) { - typr.println(i"replace singleton $param := ${ctx.typeComparer.fullLowerBound(param)}") - ctx.typerState.constraint = constraint.replace(param, - ctx.typeComparer.approximation(param, fromBelow = true)) + constraint.entry(param) match { + case TypeBounds(lo, hi) + if constraint.lower(param).isEmpty && constraint.upper(param).isEmpty && + (hi frozen_<:< lo) => + // if lower or upper is nonEmpty, the full bounds can't be equal, since + // common type params in lower and upper are eliminated through unification + val inst = ctx.typeComparer.approximation(param, fromBelow = true) + typr.println(i"replace singleton $param := $inst") + ctx.typerState.constraint = constraint.replace(param, inst) + case _ => } case _ => } From 44224834597d0b2c6ab33e30690e2e7c079f1021 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 7 May 2019 10:50:05 +0200 Subject: [PATCH 4/6] Another optimization In fact, the isLess relation is irrelevant here. If p >: L <: H and L =:= H and the constraint is satisfiable, then it's safe to replace p by L or H anyway, no matter what other parameters are known to be smaller or larger than p. --- compiler/src/dotty/tools/dotc/typer/Inferencing.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index 010fa798ade4..eb157c50e78c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -151,8 +151,7 @@ object Inferencing { val constraint = ctx.typerState.constraint constraint.entry(param) match { case TypeBounds(lo, hi) - if constraint.lower(param).isEmpty && constraint.upper(param).isEmpty && - (hi frozen_<:< lo) => + if (hi frozen_<:< lo) => // if lower or upper is nonEmpty, the full bounds can't be equal, since // common type params in lower and upper are eliminated through unification val inst = ctx.typeComparer.approximation(param, fromBelow = true) From 8332964db7390340f95c548c082c50fb4f47eba5 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 7 May 2019 12:46:41 +0200 Subject: [PATCH 5/6] Drop no-longer applicable comments --- compiler/src/dotty/tools/dotc/typer/Inferencing.scala | 2 -- tests/pos/i6385.scala | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index eb157c50e78c..1f697678f6ff 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -152,8 +152,6 @@ object Inferencing { constraint.entry(param) match { case TypeBounds(lo, hi) if (hi frozen_<:< lo) => - // if lower or upper is nonEmpty, the full bounds can't be equal, since - // common type params in lower and upper are eliminated through unification val inst = ctx.typeComparer.approximation(param, fromBelow = true) typr.println(i"replace singleton $param := $inst") ctx.typerState.constraint = constraint.replace(param, inst) diff --git a/tests/pos/i6385.scala b/tests/pos/i6385.scala index 8174a68dc253..a35dbd975e00 100644 --- a/tests/pos/i6385.scala +++ b/tests/pos/i6385.scala @@ -12,6 +12,6 @@ object X { } object app extends App { - implicitly[Tc2[X]] // ok - implicitly[Tc1[X]]//(X.Tc2Instance[Tc2]) // fails + implicitly[Tc2[X]] + implicitly[Tc1[X]] } \ No newline at end of file From 4c7ca9a0180002a89b1edb01e5d99487e10d403e Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Fri, 17 May 2019 12:32:28 +0200 Subject: [PATCH 6/6] Fix #4147: Add test case 18ce5d2aaa1a492db0f1557ebe62d0943b90f8c9 fixed it. --- tests/pos/i4147.scala | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 tests/pos/i4147.scala diff --git a/tests/pos/i4147.scala b/tests/pos/i4147.scala new file mode 100644 index 000000000000..0395e4121e0a --- /dev/null +++ b/tests/pos/i4147.scala @@ -0,0 +1,10 @@ +trait Higher[F[_]] +trait Super[A] +trait Sub[A] extends Super[A] + +object Test { + implicit def higherSub: Higher[Sub] = ??? + implicit def deriv[F[_]](implicit bla: Higher[F]): F[String] = ??? + + val x: Super[String] = deriv +}