diff options
-rw-r--r-- | src/dotty/tools/dotc/typer/Applications.scala | 1 | ||||
-rw-r--r-- | src/dotty/tools/dotc/typer/EtaExpansion.scala | 4 | ||||
-rw-r--r-- | src/dotty/tools/dotc/typer/Inferencing.scala | 152 | ||||
-rw-r--r-- | src/dotty/tools/dotc/typer/Namer.scala | 1 | ||||
-rw-r--r-- | src/dotty/tools/dotc/typer/TypeAssigner.scala | 62 | ||||
-rw-r--r-- | src/dotty/tools/dotc/typer/Typer.scala | 46 | ||||
-rw-r--r-- | test/dotc/tests.scala | 1 | ||||
-rw-r--r-- | tests/neg/i739.scala | 7 | ||||
-rw-r--r-- | tests/pos/escapingRefs.scala | 42 | ||||
-rw-r--r-- | tests/pos/i739.scala | 17 | ||||
-rw-r--r-- | tests/pos/pickleinf.scala | 9 | ||||
-rw-r--r-- | tests/run/liftedTry.scala | 3 |
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 = { |