From 3a8f70881c4e2298008cadee4def9d13cebcbe12 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 6 Aug 2015 13:09:07 -0700 Subject: [PATCH 1/6] Add test case --- tests/run/liftedTry.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/run/liftedTry.scala b/tests/run/liftedTry.scala index 5ff4add6dee3..ff9af98eca32 100644 --- a/tests/run/liftedTry.scala +++ b/tests/run/liftedTry.scala @@ -12,7 +12,7 @@ object Test { foo(try 3 catch handle) - def main(args: Array[String]): Unit = { + def main(args: Array[String]) = { assert(x == 1) assert(foo(2) == 2) assert(foo(try raise(3) catch handle) == 3) @@ -20,7 +20,6 @@ object Test { } } - object Tr { def fun(a: Int => Unit) = a(2) def foo: Int = { From 7a97e86c71090600397fd9b14a5a4111c52d8498 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 18 Sep 2015 17:22:14 +0200 Subject: [PATCH 2/6] Change algorithm that computes instantiation direction Change algorithm that determines whether type variables are minimized or maximized. We used to look only at the variance type variable in the containing type. We now also look with higher precedence at the direction from which the type variable was constrained. This is closer to what scalac does. --- .../tools/dotc/core/ConstraintHandling.scala | 2 +- src/dotty/tools/dotc/typer/Inferencing.scala | 52 ++++++++++++++++--- 2 files changed, 45 insertions(+), 9 deletions(-) diff --git a/src/dotty/tools/dotc/core/ConstraintHandling.scala b/src/dotty/tools/dotc/core/ConstraintHandling.scala index 544304e8a18d..6f0377a4d8de 100644 --- a/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -104,7 +104,7 @@ trait ConstraintHandling { up.forall(addOneBound(_, lo, isUpper = false)) } - protected final def isSubTypeWhenFrozen(tp1: Type, tp2: Type): Boolean = { + final def isSubTypeWhenFrozen(tp1: Type, tp2: Type): Boolean = { val saved = frozenConstraint frozenConstraint = true try isSubType(tp1, tp2) diff --git a/src/dotty/tools/dotc/typer/Inferencing.scala b/src/dotty/tools/dotc/typer/Inferencing.scala index 8df544dd62c6..6c8bf49ef321 100644 --- a/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/src/dotty/tools/dotc/typer/Inferencing.scala @@ -44,8 +44,20 @@ trait Inferencing { this: Checking => else throw new Error(i"internal error: type of $what $tp is not fully defined, pos = $pos") // !!! DEBUG /** The accumulator which forces type variables using the policy encoded in `force` - * and returns whether the type is fully defined. Two phases: - * 1st Phase: Try to instantiate covariant and non-variant type variables to + * and returns whether the type is fully defined. The direction in which + * a type variable is instantiated is determined as follows: + * 1. T is minimized if the constraint over T is only from below (i.e. + * constrained lower bound != given lower bound and + * constrained upper bound == given upper bound). + * 2. T is maximized if the constraint over T is only from above (i.e. + * constrained upper bound != given upper bound and + * constrained lower bound == given lower bound). + * If (1) and (2) do not apply: + * 3. T is maximized if it appears only contravariantly in the given type. + * 4. T is minimized in all other cases. + * + * The instantiation is done in two phases: + * 1st Phase: Try to instantiate minimizable type variables to * their lower bound. Record whether successful. * 2nd Phase: If first phase was successful, instantiate all remaining type variables * to their upper bound. @@ -63,12 +75,19 @@ trait Inferencing { this: Checking => case tvar: TypeVar if !tvar.isInstantiated => if (force == ForceDegree.none) false else { - val minimize = - variance >= 0 && !( - force == ForceDegree.noBottom && - isBottomType(ctx.typeComparer.approximation(tvar.origin, fromBelow = true))) - if (minimize) instantiate(tvar, fromBelow = true) - else toMaximize = true + val direction = instDirection(tvar.origin) + if (direction != 0) { + if (direction > 0) println(s"inst $tvar dir = up") + instantiate(tvar, direction < 0) + } + else { + val minimize = + variance >= 0 && !( + force == ForceDegree.noBottom && + isBottomType(ctx.typeComparer.approximation(tvar.origin, fromBelow = true))) + if (minimize) instantiate(tvar, fromBelow = true) + else toMaximize = true + } foldOver(x, tvar) } case tp => @@ -93,6 +112,23 @@ trait Inferencing { this: Checking => } } + /** The instantiation direction for given poly param computed + * from the constraint: + * @return 1 (maximize) if constraint is uniformly from above, + * -1 (minimize) if constraint is uniformly from below, + * 0 if unconstrained, or constraint is from below and above. + */ + private def instDirection(param: PolyParam)(implicit ctx: Context): Int = { + val constrained = ctx.typerState.constraint.fullBounds(param) + val original = param.binder.paramBounds(param.paramNum) + val cmp = ctx.typeComparer + val approxBelow = + if (!cmp.isSubTypeWhenFrozen(constrained.lo, original.lo)) 1 else 0 + val approxAbove = + if (!cmp.isSubTypeWhenFrozen(original.hi, constrained.hi)) 1 else 0 + approxAbove - approxBelow + } + def isBottomType(tp: Type)(implicit ctx: Context) = tp == defn.NothingType || tp == defn.NullType From 493fbbdd6cd0ca7fecd7e34f963563fe58e1f877 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 18 Sep 2015 17:37:56 +0200 Subject: [PATCH 3/6] Fixes #739 by adding the following rule: Before typing an implicit parameter list of a method m, instantiate all type parameters of m that occur in the type of some preceding value parameter of m. --- src/dotty/tools/dotc/typer/Inferencing.scala | 44 +++++++++++++++++--- src/dotty/tools/dotc/typer/Typer.scala | 2 + 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/src/dotty/tools/dotc/typer/Inferencing.scala b/src/dotty/tools/dotc/typer/Inferencing.scala index 6c8bf49ef321..a5cf8c4eb2af 100644 --- a/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/src/dotty/tools/dotc/typer/Inferencing.scala @@ -43,6 +43,11 @@ trait Inferencing { this: Checking => if (isFullyDefined(tp, ForceDegree.all)) tp else throw new Error(i"internal error: type of $what $tp is not fully defined, pos = $pos") // !!! DEBUG + + /** Instantiate selected type variables `tvars` in type `tp` */ + def instantiateSelected(tp: Type, tvars: List[Type])(implicit ctx: Context): Unit = + new IsFullyDefinedAccumulator(new ForceDegree.Value(tvars.contains)).process(tp) + /** The accumulator which forces type variables using the policy encoded in `force` * and returns whether the type is fully defined. The direction in which * a type variable is instantiated is determined as follows: @@ -73,8 +78,7 @@ trait Inferencing { this: Checking => case _: WildcardType | _: ProtoType => false case tvar: TypeVar if !tvar.isInstantiated => - if (force == ForceDegree.none) false - else { + force.appliesTo(tvar) && { val direction = instDirection(tvar.origin) if (direction != 0) { if (direction > 0) println(s"inst $tvar dir = up") @@ -111,6 +115,33 @@ trait Inferencing { this: Checking => res } } + + /** If `tree`'s type is of the form + * + * e [T1, ..., Tn] (ps1)...(psn) + * + * the list of uninstantiated type variables matching one of `T1`, ..., `Tn` + * which also appear in one of the parameter sections `ps1`, ..., `psn`, otherwise Nil. + */ + def tvarsInParams(tree: Tree)(implicit ctx: Context): List[TypeVar] = { + def occursInParam(mtp: Type, tvar: TypeVar, secCount: Int): Boolean = mtp match { + case mtp: MethodType => + secCount > 0 && ( + mtp.paramTypes.exists(tvar.occursIn) || + occursInParam(mtp.resultType, tvar, secCount - 1)) + case _ => false + } + def collect(tree: Tree, secCount: Int): List[TypeVar] = tree match { + case Apply(fn, _) => collect(fn, secCount + 1) + case TypeApply(_, targs) => + targs.tpes.collect { + case tvar: TypeVar + if !tvar.isInstantiated && occursInParam(tree.tpe, tvar, secCount) => tvar + } + case _ => Nil + } + collect(tree, 0) + } /** The instantiation direction for given poly param computed * from the constraint: @@ -293,9 +324,10 @@ trait Inferencing { this: Checking => } /** An enumeration controlling the degree of forcing in "is-dully-defined" checks. */ -@sharable object ForceDegree extends Enumeration { - val none, // don't force type variables - noBottom, // force type variables, fail if forced to Nothing or Null - all = Value // force type variables, don't fail +@sharable object ForceDegree { + class Value(val appliesTo: TypeVar => Boolean) + val none = new Value(_ => false) + val all = new Value(_ => true) + val noBottom = new Value(_ => true) } diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index a2c49cdd9751..4dfd69203323 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -1312,6 +1312,8 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit case wtp: ExprType => adaptInterpolated(tree.withType(wtp.resultType), pt, original) case wtp: ImplicitMethodType if constrainResult(wtp, followAlias(pt)) => + val tvarsToInstantiate = tvarsInParams(tree) + wtp.paramTypes.foreach(instantiateSelected(_, tvarsToInstantiate)) val constr = ctx.typerState.constraint def addImplicitArgs = { def implicitArgError(msg: => String): Tree = { From 5e48d1c25e9fe1f5c6b962a6edb209e587150b86 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 9 Aug 2015 15:22:42 -0700 Subject: [PATCH 4/6] Add test case --- tests/pos/i739.scala | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 tests/pos/i739.scala diff --git a/tests/pos/i739.scala b/tests/pos/i739.scala new file mode 100644 index 000000000000..340478f4fa5c --- /dev/null +++ b/tests/pos/i739.scala @@ -0,0 +1,10 @@ +class Foo + +object Test { + def foo[T](x: T)(implicit ev: T): T = ??? + + def test: Unit = { + implicit val evidence: Foo = new Foo + foo(new Foo) + } +} From 7f983665985d93c735d519f7d24d9ba072b26cd4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 9 Aug 2015 15:29:20 -0700 Subject: [PATCH 5/6] Added neg test (scalac and dotty both produce an error here) --- tests/neg/i739.scala | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 tests/neg/i739.scala diff --git a/tests/neg/i739.scala b/tests/neg/i739.scala new file mode 100644 index 000000000000..5385fa42cced --- /dev/null +++ b/tests/neg/i739.scala @@ -0,0 +1,7 @@ +class Foo[A, B] +class Test { + implicit val f: Foo[Int, String] = ??? + def t[A, B >: A](a: A)(implicit f: Foo[A, B]) = ??? + t(1) // error +} + From 22a2c79adf3acb8b5dd341b552e499bced58c537 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 18 Sep 2015 17:51:45 +0200 Subject: [PATCH 6/6] Generalize set of typevars instantiated before implicit search We now also consider type variables in a selection prefix of the application. The test case was augmented to include a snippet which only succeeds under the generalization. --- src/dotty/tools/dotc/typer/Inferencing.scala | 57 ++++++++++++-------- tests/pos/i739.scala | 7 +++ 2 files changed, 42 insertions(+), 22 deletions(-) diff --git a/src/dotty/tools/dotc/typer/Inferencing.scala b/src/dotty/tools/dotc/typer/Inferencing.scala index a5cf8c4eb2af..0a76f45c5089 100644 --- a/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/src/dotty/tools/dotc/typer/Inferencing.scala @@ -17,6 +17,7 @@ import Decorators._ import Uniques._ import ErrorReporting.{errorType, DiagnosticString} import config.Printers._ +import annotation.tailrec import collection.mutable trait Inferencing { this: Checking => @@ -115,32 +116,44 @@ trait Inferencing { this: Checking => res } } - - /** If `tree`'s type is of the form - * - * e [T1, ..., Tn] (ps1)...(psn) - * - * the list of uninstantiated type variables matching one of `T1`, ..., `Tn` - * which also appear in one of the parameter sections `ps1`, ..., `psn`, otherwise Nil. + + /** The list of uninstantiated type variables bound by some prefix of type `T` which + * occur in at least one formal parameter type of a prefix application. + * Considered prefixes are: + * - The function `f` of an application node `f(e1, .., en)` + * - The function `f` of a type application node `f[T1, ..., Tn]` + * - The prefix `p` of a selection `p.f`. + * - The result expression `e` of a block `{s1; .. sn; e}`. */ def tvarsInParams(tree: Tree)(implicit ctx: Context): List[TypeVar] = { - def occursInParam(mtp: Type, tvar: TypeVar, secCount: Int): Boolean = mtp match { - case mtp: MethodType => - secCount > 0 && ( - mtp.paramTypes.exists(tvar.occursIn) || - occursInParam(mtp.resultType, tvar, secCount - 1)) - case _ => false - } - def collect(tree: Tree, secCount: Int): List[TypeVar] = tree match { - case Apply(fn, _) => collect(fn, secCount + 1) - case TypeApply(_, targs) => - targs.tpes.collect { - case tvar: TypeVar - if !tvar.isInstantiated && occursInParam(tree.tpe, tvar, secCount) => tvar + @tailrec def boundVars(tree: Tree, acc: List[TypeVar]): List[TypeVar] = tree match { + case Apply(fn, _) => boundVars(fn, acc) + case TypeApply(fn, targs) => + val tvars = targs.tpes.collect { + case tvar: TypeVar if !tvar.isInstantiated => tvar } - case _ => Nil + boundVars(fn, acc ::: tvars) + case Select(pre, _) => boundVars(pre, acc) + case Block(_, expr) => boundVars(expr, acc) + case _ => acc } - collect(tree, 0) + @tailrec def occurring(tree: Tree, toTest: List[TypeVar], acc: List[TypeVar]): List[TypeVar] = + if (toTest.isEmpty) acc + else tree match { + case Apply(fn, _) => + fn.tpe match { + case mtp: MethodType => + val (occ, nocc) = toTest.partition(tvar => mtp.paramTypes.exists(tvar.occursIn)) + occurring(fn, nocc, occ ::: acc) + case _ => + occurring(fn, toTest, acc) + } + case TypeApply(fn, targs) => occurring(fn, toTest, acc) + case Select(pre, _) => occurring(pre, toTest, acc) + case Block(_, expr) => occurring(expr, toTest, acc) + case _ => acc + } + occurring(tree, boundVars(tree, Nil), Nil) } /** The instantiation direction for given poly param computed diff --git a/tests/pos/i739.scala b/tests/pos/i739.scala index 340478f4fa5c..61fed4e5dee4 100644 --- a/tests/pos/i739.scala +++ b/tests/pos/i739.scala @@ -3,8 +3,15 @@ class Foo object Test { def foo[T](x: T)(implicit ev: T): T = ??? + class Fn[T] { + def invoke(implicit ev: T): T = ??? + } + + def bar[T](x: T): Fn[T] = ??? + def test: Unit = { implicit val evidence: Foo = new Foo foo(new Foo) + bar(new Foo).invoke } }