diff --git a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index 85932d11295a..77555154a256 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -222,72 +222,6 @@ object Inferencing { case _ => NoType } - /** 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, - * approximate it by its lower bound. Otherwise, if it appears contravariantly - * in type `tp` approximate it by its upper bound. - * @param ownedBy if it is different from NoSymbol, all type variables owned by - * `ownedBy` qualify, independent of position. - * Without that second condition, it can be that certain variables escape - * interpolation, for instance when their tree was eta-lifted, so - * the typechecked tree is no longer the tree in which the variable - * was declared. A concrete example of this phenomenon can be - * observed when compiling core.TypeOps#asSeenFrom. - */ - def interpolateUndetVars(tree: Tree, ownedBy: Symbol)(implicit ctx: Context): Unit = { - val constraint = ctx.typerState.constraint - val qualifies = (tvar: TypeVar) => - (tree contains tvar.bindingTree) || ownedBy.exists && tvar.owner == ownedBy - def interpolate() = Stats.track("interpolateUndetVars") { - val tp = tree.tpe.widen - constr.println(s"interpolate undet vars in ${tp.show}, pos = ${tree.pos}, mode = ${ctx.mode}, undets = ${constraint.uninstVars map (tvar => s"${tvar.show}@${tvar.bindingTree.pos}")}") - constr.println(s"qualifying undet vars: ${constraint.uninstVars filter qualifies map (tvar => s"$tvar / ${tvar.show}")}, constraint: ${constraint.show}") - - val vs = variances(tp, qualifies) - val hasUnreportedErrors = ctx.typerState.reporter match { - case r: StoreReporter if r.hasErrors => true - case _ => false - } - // Avoid interpolating variables if typerstate has unreported errors. - // Reason: The errors might reflect unsatisfiable constraints. In that - // case interpolating without taking account the constraints risks producing - // nonsensical types that then in turn produce incomprehensible errors. - // An example is in neg/i1240.scala. Without the condition in the next code line - // we get for - // - // val y: List[List[String]] = List(List(1)) - // - // i1430.scala:5: error: type mismatch: - // found : Int(1) - // required: Nothing - // val y: List[List[String]] = List(List(1)) - // ^ - // With the condition, we get the much more sensical: - // - // i1430.scala:5: error: type mismatch: - // found : Int(1) - // required: String - // val y: List[List[String]] = List(List(1)) - if (!hasUnreportedErrors) - vs foreachBinding { (tvar, v) => - if (v != 0 && ctx.typerState.constraint.contains(tvar)) { - // previous interpolations could have already instantiated `tvar` - // through unification, that's why we have to check again whether `tvar` - // is contained in the current constraint. - typr.println(s"interpolate ${if (v == 1) "co" else "contra"}variant ${tvar.show} in ${tp.show}") - tvar.instantiate(fromBelow = v == 1) - } - } - for (tvar <- constraint.uninstVars) - if (!(vs contains tvar) && qualifies(tvar)) { - typr.println(s"instantiating non-occurring ${tvar.show} in ${tp.show} / $tp") - tvar.instantiate(fromBelow = true) - } - } - if (constraint.uninstVars exists qualifies) interpolate() - } - /** Instantiate undetermined type variables to that type `tp` is * maximized and return None. If this is not possible, because a non-variant * typevar is not uniquely determined, return that typevar in a Some. @@ -375,6 +309,93 @@ object Inferencing { } } +trait Inferencing { this: Typer => + import Inferencing._ + import tpd._ + + /** 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, + * approximate it by its lower bound. Otherwise, if it appears contravariantly + * in type `tp` approximate it by its upper bound. + * @param ownedBy if it is different from NoSymbol, all type variables owned by + * `ownedBy` qualify, independent of position. + * Without that second condition, it can be that certain variables escape + * interpolation, for instance when their tree was eta-lifted, so + * the typechecked tree is no longer the tree in which the variable + * was declared. A concrete example of this phenomenon can be + * observed when compiling core.TypeOps#asSeenFrom. + */ + def interpolateUndetVars(tree: Tree, ownedBy: Symbol, pt: Type)(implicit ctx: Context): Unit = { + val constraint = ctx.typerState.constraint + val qualifies = (tvar: TypeVar) => + (tree contains tvar.bindingTree) || ownedBy.exists && tvar.owner == ownedBy + def interpolate() = Stats.track("interpolateUndetVars") { + val tp = tree.tpe.widen + constr.println(s"interpolate undet vars in ${tp.show}, pos = ${tree.pos}, mode = ${ctx.mode}, undets = ${constraint.uninstVars map (tvar => s"${tvar.show}@${tvar.bindingTree.pos}")}") + constr.println(s"qualifying undet vars: ${constraint.uninstVars filter qualifies map (tvar => s"$tvar / ${tvar.show}")}, constraint: ${constraint.show}") + + val vs = variances(tp, qualifies) + val hasUnreportedErrors = ctx.typerState.reporter match { + case r: StoreReporter if r.hasErrors => true + case _ => false + } + + var isConstrained = false + def ensureConstrained() = + if (!isConstrained) { + isConstrained = true + tree match { + case tree: Apply => // already constrained + case _ => tree.tpe match { + case _: MethodOrPoly => // already constrained + case tp => constrainResult(tp, pt) + } + } + } + + // Avoid interpolating variables if typerstate has unreported errors. + // Reason: The errors might reflect unsatisfiable constraints. In that + // case interpolating without taking account the constraints risks producing + // nonsensical types that then in turn produce incomprehensible errors. + // An example is in neg/i1240.scala. Without the condition in the next code line + // we get for + // + // val y: List[List[String]] = List(List(1)) + // + // i1430.scala:5: error: type mismatch: + // found : Int(1) + // required: Nothing + // val y: List[List[String]] = List(List(1)) + // ^ + // With the condition, we get the much more sensical: + // + // i1430.scala:5: error: type mismatch: + // found : Int(1) + // required: String + // val y: List[List[String]] = List(List(1)) + if (!hasUnreportedErrors) + vs foreachBinding { (tvar, v) => + if (v != 0 && ctx.typerState.constraint.contains(tvar)) { + // previous interpolations could have already instantiated `tvar` + // through unification, that's why we have to check again whether `tvar` + // is contained in the current constraint. + typr.println(s"interpolate ${if (v == 1) "co" else "contra"}variant ${tvar.show} in ${tp.show}") + ensureConstrained() + tvar.instantiate(fromBelow = v == 1) + } + } + for (tvar <- constraint.uninstVars) + if (!(vs contains tvar) && qualifies(tvar)) { + typr.println(s"instantiating non-occurring ${tvar.show} in ${tp.show} / $tp") + ensureConstrained() + tvar.instantiate(fromBelow = true) + } + } + if (constraint.uninstVars exists qualifies) interpolate() + } +} + /** An enumeration controlling the degree of forcing in "is-dully-defined" checks. */ @sharable object ForceDegree { class Value(val appliesTo: TypeVar => Boolean, val minimizeAll: Boolean) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index a562e69acc00..0c8fc4a183b1 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -66,7 +66,14 @@ object Typer { private val InsertedApply = new Property.Key[Unit] } -class Typer extends Namer with TypeAssigner with Applications with Implicits with Dynamic with Checking with Docstrings { +class Typer extends Namer + with TypeAssigner + with Applications + with Implicits + with Inferencing + with Dynamic + with Checking + with Docstrings { import Typer._ import tpd.{cpy => _, _} @@ -1929,8 +1936,8 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit /*>|>*/ trace(i"adapting $tree of type ${tree.tpe} to $pt", typr, show = true) /*<|<*/ { if (!tree.denot.isOverloaded) { // for overloaded trees: resolve overloading before simplifying - if (tree.isDef) interpolateUndetVars(tree, tree.symbol) - else if (!tree.tpe.widen.isInstanceOf[LambdaType]) interpolateUndetVars(tree, NoSymbol) + if (tree.isDef) interpolateUndetVars(tree, tree.symbol, pt) + else if (!tree.tpe.widen.isInstanceOf[LambdaType]) interpolateUndetVars(tree, NoSymbol, pt) tree.overwriteType(tree.tpe.simplified) } adaptInterpolated(tree, pt) diff --git a/tests/neg/i2997.scala b/tests/neg/i2997.scala new file mode 100644 index 000000000000..b89073d6c179 --- /dev/null +++ b/tests/neg/i2997.scala @@ -0,0 +1,9 @@ +case class Foo[T <: Int with Singleton](t : T) + +object Test { + val one = 1 + final val final_one = 1 + val a : 1 = Foo(1).t + val b : Int = Foo(one).t // error: does not conform to upper bound Int & Singleton + val c : 1 = Foo(final_one).t +} diff --git a/tests/pos/i2997.scala b/tests/pos/i2997.scala new file mode 100644 index 000000000000..caab612dfce5 --- /dev/null +++ b/tests/pos/i2997.scala @@ -0,0 +1,8 @@ +case class Foo[T <: Int with Singleton](t : T) + +object Test { + val one = 1 + final val final_one = 1 + val a : 1 = Foo(1).t + val c : 1 = Foo(final_one).t +} diff --git a/tests/pos/i2998.scala b/tests/pos/i2998.scala new file mode 100644 index 000000000000..f3d1800b31cd --- /dev/null +++ b/tests/pos/i2998.scala @@ -0,0 +1,13 @@ + class Foo[T] { + def zero: T = ??? + def one(): T = ??? + val two: T = ??? + } + + object Test { + def foo[T](x: T): Foo[T] = new Foo + + val b: 1 = foo(1).one() // OK + val a: 1 = foo(1).zero // Fails: Found: Int, required: Int(1) + val c: 1 = foo(1).two // Fails: Found: Int, required: Int(1) + }