aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/dotty/tools/dotc/typer/Applications.scala1
-rw-r--r--src/dotty/tools/dotc/typer/EtaExpansion.scala4
-rw-r--r--src/dotty/tools/dotc/typer/Inferencing.scala152
-rw-r--r--src/dotty/tools/dotc/typer/Namer.scala1
-rw-r--r--src/dotty/tools/dotc/typer/TypeAssigner.scala62
-rw-r--r--src/dotty/tools/dotc/typer/Typer.scala46
-rw-r--r--test/dotc/tests.scala1
-rw-r--r--tests/neg/i739.scala7
-rw-r--r--tests/pos/escapingRefs.scala42
-rw-r--r--tests/pos/i739.scala17
-rw-r--r--tests/pos/pickleinf.scala9
-rw-r--r--tests/run/liftedTry.scala3
12 files changed, 264 insertions, 81 deletions
diff --git a/src/dotty/tools/dotc/typer/Applications.scala b/src/dotty/tools/dotc/typer/Applications.scala
index c7d8acb37..87ad0831c 100644
--- a/src/dotty/tools/dotc/typer/Applications.scala
+++ b/src/dotty/tools/dotc/typer/Applications.scala
@@ -21,6 +21,7 @@ import Names._
import StdNames._
import ProtoTypes._
import EtaExpansion._
+import Inferencing._
import collection.mutable
import config.Printers._
import TypeApplications._
diff --git a/src/dotty/tools/dotc/typer/EtaExpansion.scala b/src/dotty/tools/dotc/typer/EtaExpansion.scala
index 4fa3d78eb..89415024e 100644
--- a/src/dotty/tools/dotc/typer/EtaExpansion.scala
+++ b/src/dotty/tools/dotc/typer/EtaExpansion.scala
@@ -13,6 +13,7 @@ import Decorators._
import Names._
import StdNames._
import Trees._
+import Inferencing._
import util.Positions._
import collection.mutable
@@ -24,7 +25,8 @@ object EtaExpansion {
if (isPureExpr(expr)) expr
else {
val name = ctx.freshName(prefix).toTermName
- val sym = ctx.newSymbol(ctx.owner, name, EmptyFlags, expr.tpe.widen, coord = positionCoord(expr.pos))
+ val liftedType = fullyDefinedType(expr.tpe.widen, "lifted expression", expr.pos)
+ val sym = ctx.newSymbol(ctx.owner, name, EmptyFlags, liftedType, coord = positionCoord(expr.pos))
defs += ValDef(sym, expr)
ref(sym.valRef)
}
diff --git a/src/dotty/tools/dotc/typer/Inferencing.scala b/src/dotty/tools/dotc/typer/Inferencing.scala
index 8df544dd6..81a302717 100644
--- a/src/dotty/tools/dotc/typer/Inferencing.scala
+++ b/src/dotty/tools/dotc/typer/Inferencing.scala
@@ -17,9 +17,10 @@ import Decorators._
import Uniques._
import ErrorReporting.{errorType, DiagnosticString}
import config.Printers._
+import annotation.tailrec
import collection.mutable
-trait Inferencing { this: Checking =>
+object Inferencing {
import tpd._
@@ -43,9 +44,26 @@ 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. 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.
@@ -61,14 +79,20 @@ trait Inferencing { this: Checking =>
case _: WildcardType | _: ProtoType =>
false
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
+ force.appliesTo(tvar) && {
+ 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 +117,62 @@ trait Inferencing { this: Checking =>
}
}
+ /** 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] = {
+ @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
+ }
+ boundVars(fn, acc ::: tvars)
+ case Select(pre, _) => boundVars(pre, acc)
+ case Block(_, expr) => boundVars(expr, acc)
+ case _ => acc
+ }
+ @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
+ * 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
@@ -117,47 +197,6 @@ trait Inferencing { this: Checking =>
case _ => NoType
}
- /** Ensure that the first type in a list of parent types Ps points to a non-trait class.
- * If that's not already the case, add one. The added class type CT is determined as follows.
- * First, let C be the unique class such that
- * - there is a parent P_i such that P_i derives from C, and
- * - for every class D: If some parent P_j, j <= i derives from D, then C derives from D.
- * Then, let CT be the smallest type which
- * - has C as its class symbol, and
- * - for all parents P_i: If P_i derives from C then P_i <:< CT.
- */
- def ensureFirstIsClass(parents: List[Type])(implicit ctx: Context): List[Type] = {
- def realClassParent(cls: Symbol): ClassSymbol =
- if (!cls.isClass) defn.ObjectClass
- else if (!(cls is Trait)) cls.asClass
- else cls.asClass.classParents match {
- case parentRef :: _ => realClassParent(parentRef.symbol)
- case nil => defn.ObjectClass
- }
- def improve(candidate: ClassSymbol, parent: Type): ClassSymbol = {
- val pcls = realClassParent(parent.classSymbol)
- if (pcls derivesFrom candidate) pcls else candidate
- }
- parents match {
- case p :: _ if p.classSymbol.isRealClass => parents
- case _ =>
- val pcls = (defn.ObjectClass /: parents)(improve)
- typr.println(i"ensure first is class $parents%, % --> ${parents map (_ baseTypeWithArgs pcls)}%, %")
- val ptype = ctx.typeComparer.glb(
- defn.ObjectType :: (parents map (_ baseTypeWithArgs pcls)))
- ptype :: parents
- }
- }
-
- /** Ensure that first parent tree refers to a real class. */
- def ensureFirstIsClass(parents: List[Tree], pos: Position)(implicit ctx: Context): List[Tree] = parents match {
- case p :: ps if p.tpe.classSymbol.isRealClass => parents
- case _ =>
- // add synthetic class type
- val first :: _ = ensureFirstIsClass(parents.tpes)
- TypeTree(checkFeasible(first, pos, d"\n in inferred parent $first")).withPos(pos) :: parents
- }
-
/** Interpolate those undetermined type variables in the widened type of this tree
* which are introduced by type application contained in the tree.
* If such a variable appears covariantly in type `tp` or does not appear at all,
@@ -257,9 +296,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/Namer.scala b/src/dotty/tools/dotc/typer/Namer.scala
index c1341a9ae..d97bbff91 100644
--- a/src/dotty/tools/dotc/typer/Namer.scala
+++ b/src/dotty/tools/dotc/typer/Namer.scala
@@ -16,6 +16,7 @@ import ErrorReporting._
import tpd.ListOfTreeDecorator
import config.Printers._
import Annotations._
+import Inferencing._
import transform.ValueClasses._
import language.implicitConversions
diff --git a/src/dotty/tools/dotc/typer/TypeAssigner.scala b/src/dotty/tools/dotc/typer/TypeAssigner.scala
index ba8d44110..7225ede14 100644
--- a/src/dotty/tools/dotc/typer/TypeAssigner.scala
+++ b/src/dotty/tools/dotc/typer/TypeAssigner.scala
@@ -31,35 +31,40 @@ trait TypeAssigner {
/** An upper approximation of the given type `tp` that does not refer to any symbol in `symsToAvoid`.
* Approximation steps are:
*
- * - follow aliases if the original refers to a forbidden symbol
+ * - follow aliases and upper bounds if the original refers to a forbidden symbol
* - widen termrefs that refer to a forbidden symbol
* - replace ClassInfos of forbidden classes by the intersection of their parents, refined by all
* non-private fields, methods, and type members.
+ * - if the prefix of a class refers to a forbidden symbol, first try to replace the prefix,
+ * if this is not possible, replace the ClassInfo as above.
* - drop refinements referring to a forbidden symbol.
*/
def avoid(tp: Type, symsToAvoid: => List[Symbol])(implicit ctx: Context): Type = {
val widenMap = new TypeMap {
lazy val forbidden = symsToAvoid.toSet
- def toAvoid(tp: Type): Boolean = tp match {
- case tp: TermRef =>
- val sym = tp.symbol
- sym.exists && (
- sym.owner.isTerm && (forbidden contains sym)
- || !(sym.owner is Package) && toAvoid(tp.prefix)
- )
- case tp: TypeRef =>
- forbidden contains tp.symbol
- case _ =>
- false
- }
- def apply(tp: Type) = tp match {
+ def toAvoid(tp: Type): Boolean =
+ // TODO: measure the cost of using `existsPart`, and if necessary replace it
+ // by a `TypeAccumulator` where we have set `stopAtStatic = true`.
+ tp existsPart {
+ case tp: NamedType =>
+ forbidden contains tp.symbol
+ case _ =>
+ false
+ }
+ def apply(tp: Type): Type = tp match {
case tp: TermRef if toAvoid(tp) && variance > 0 =>
apply(tp.info.widenExpr)
- case tp: TypeRef if (forbidden contains tp.symbol) || toAvoid(tp.prefix) =>
+ case tp: TypeRef if toAvoid(tp) =>
tp.info match {
case TypeAlias(ref) =>
apply(ref)
case info: ClassInfo if variance > 0 =>
+ if (!(forbidden contains tp.symbol)) {
+ val prefix = apply(tp.prefix)
+ val tp1 = tp.derivedSelect(prefix)
+ if (tp1.typeSymbol.exists)
+ return tp1
+ }
val parentType = info.instantiatedParents.reduceLeft(ctx.typeComparer.andType(_, _))
def addRefinement(parent: Type, decl: Symbol) = {
val inherited =
@@ -82,13 +87,28 @@ trait TypeAssigner {
case _ =>
mapOver(tp)
}
- case tp: RefinedType =>
- val tp1 @ RefinedType(parent1, _) = mapOver(tp)
- if (tp1.refinedInfo.existsPart(toAvoid) && variance > 0) {
- typr.println(s"dropping refinement from $tp1")
- parent1
+ case tp @ RefinedType(parent, name) if variance > 0 =>
+ // The naive approach here would be to first approximate the parent,
+ // but if the base type of the approximated parent is different from
+ // the current base type, then the current refinement won't be valid
+ // if it's a type parameter refinement.
+ // Therefore we first approximate the base type, then use `baseArgInfos`
+ // to get correct refinements for the approximated base type, then
+ // recursively approximate the resulting type.
+ val base = tp.unrefine
+ if (toAvoid(base)) {
+ val base1 = apply(base)
+ apply(base1.appliedTo(tp.baseArgInfos(base1.typeSymbol)))
+ } else {
+ val parent1 = apply(tp.parent)
+ val refinedInfo1 = apply(tp.refinedInfo)
+ if (toAvoid(refinedInfo1)) {
+ typr.println(s"dropping refinement from $tp")
+ parent1
+ } else {
+ tp.derivedRefinedType(parent1, name, refinedInfo1)
+ }
}
- else tp1
case tp: TypeVar if ctx.typerState.constraint.contains(tp) =>
val lo = ctx.typerState.constraint.fullLowerBound(tp.origin)
val lo1 = avoid(lo, symsToAvoid)
diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala
index d94505e29..fbdfef930 100644
--- a/src/dotty/tools/dotc/typer/Typer.scala
+++ b/src/dotty/tools/dotc/typer/Typer.scala
@@ -21,6 +21,7 @@ import Flags._
import Decorators._
import ErrorReporting._
import Checking._
+import Inferencing._
import EtaExpansion.etaExpand
import dotty.tools.dotc.transform.Erasure.Boxing
import util.Positions._
@@ -55,7 +56,7 @@ object Typer {
assert(tree.pos.exists, s"position not set for $tree # ${tree.uniqueId}")
}
-class Typer extends Namer with TypeAssigner with Applications with Implicits with Inferencing with Checking {
+class Typer extends Namer with TypeAssigner with Applications with Implicits with Checking {
import Typer._
import tpd.{cpy => _, _}
@@ -977,6 +978,47 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
// 4. Polymorphic type defs override nothing.
}
+ /** Ensure that the first type in a list of parent types Ps points to a non-trait class.
+ * If that's not already the case, add one. The added class type CT is determined as follows.
+ * First, let C be the unique class such that
+ * - there is a parent P_i such that P_i derives from C, and
+ * - for every class D: If some parent P_j, j <= i derives from D, then C derives from D.
+ * Then, let CT be the smallest type which
+ * - has C as its class symbol, and
+ * - for all parents P_i: If P_i derives from C then P_i <:< CT.
+ */
+ def ensureFirstIsClass(parents: List[Type])(implicit ctx: Context): List[Type] = {
+ def realClassParent(cls: Symbol): ClassSymbol =
+ if (!cls.isClass) defn.ObjectClass
+ else if (!(cls is Trait)) cls.asClass
+ else cls.asClass.classParents match {
+ case parentRef :: _ => realClassParent(parentRef.symbol)
+ case nil => defn.ObjectClass
+ }
+ def improve(candidate: ClassSymbol, parent: Type): ClassSymbol = {
+ val pcls = realClassParent(parent.classSymbol)
+ if (pcls derivesFrom candidate) pcls else candidate
+ }
+ parents match {
+ case p :: _ if p.classSymbol.isRealClass => parents
+ case _ =>
+ val pcls = (defn.ObjectClass /: parents)(improve)
+ typr.println(i"ensure first is class $parents%, % --> ${parents map (_ baseTypeWithArgs pcls)}%, %")
+ val ptype = ctx.typeComparer.glb(
+ defn.ObjectType :: (parents map (_ baseTypeWithArgs pcls)))
+ ptype :: parents
+ }
+ }
+
+ /** Ensure that first parent tree refers to a real class. */
+ def ensureFirstIsClass(parents: List[Tree], pos: Position)(implicit ctx: Context): List[Tree] = parents match {
+ case p :: ps if p.tpe.classSymbol.isRealClass => parents
+ case _ =>
+ // add synthetic class type
+ val first :: _ = ensureFirstIsClass(parents.tpes)
+ TypeTree(checkFeasible(first, pos, d"\n in inferred parent $first")).withPos(pos) :: parents
+ }
+
/** If this is a real class, make sure its first parent is a
* constructor call. Cannot simply use a type. Overridden in ReTyper.
*/
@@ -1352,6 +1394,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 = {
diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala
index b22da9b5d..89687d16c 100644
--- a/test/dotc/tests.scala
+++ b/test/dotc/tests.scala
@@ -53,6 +53,7 @@ class tests extends CompilerTest {
// This directory doesn't exist anymore
// @Test def pickle_pickling = compileDir(coreDir, "pickling", testPickling)
@Test def pickle_ast = compileDir(dotcDir, "ast", testPickling)
+ @Test def pickle_inf = compileFile(posDir, "pickleinf", testPickling)
//@Test def pickle_core = compileDir(dotcDir, "core", testPickling, xerrors = 2) // two spurious comparison errors in Types and TypeOps
diff --git a/tests/neg/i739.scala b/tests/neg/i739.scala
new file mode 100644
index 000000000..5385fa42c
--- /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
+}
+
diff --git a/tests/pos/escapingRefs.scala b/tests/pos/escapingRefs.scala
new file mode 100644
index 000000000..1b1deb8de
--- /dev/null
+++ b/tests/pos/escapingRefs.scala
@@ -0,0 +1,42 @@
+class Outer {
+ class Inner {
+ class Inner2
+ }
+}
+
+class HasA { type A }
+
+class Foo[A]
+
+object Test {
+ def test = {
+ val a: Outer#Inner = {
+ val o = new Outer
+ new o.Inner
+ }
+
+ val b: Outer#Inner#Inner2 = {
+ val o = new Outer
+ val i = new o.Inner
+ new i.Inner2
+ }
+
+ val c: HasA { type A = Int } = {
+ val h = new HasA {
+ type A = Int
+ }
+ val x: HasA { type A = h.A } = h
+ x
+ }
+
+ val d: Foo[Int] = {
+ class Bar[B] extends Foo[B]
+ new Bar[Int]
+ }
+
+ val e: Foo[_] = {
+ class Bar[B] extends Foo[B]
+ new Bar[Int]: Bar[_ <: Int]
+ }
+ }
+}
diff --git a/tests/pos/i739.scala b/tests/pos/i739.scala
new file mode 100644
index 000000000..61fed4e5d
--- /dev/null
+++ b/tests/pos/i739.scala
@@ -0,0 +1,17 @@
+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
+ }
+}
diff --git a/tests/pos/pickleinf.scala b/tests/pos/pickleinf.scala
new file mode 100644
index 000000000..9132f1a17
--- /dev/null
+++ b/tests/pos/pickleinf.scala
@@ -0,0 +1,9 @@
+class Bar[N] {
+ def bar(name: N, dummy: Int = 42): N = name
+}
+
+object Test {
+ def test(): Unit = {
+ (new Bar).bar(10)
+ }
+}
diff --git a/tests/run/liftedTry.scala b/tests/run/liftedTry.scala
index 5ff4add6d..ff9af98ec 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 = {