Skip to content

Commit fbe4237

Browse files
authored
Merge pull request #3774 from dotty-staging/fix-#2998
Fix #2998: Constrain type from above before interpolating variables
2 parents b85a951 + b22f22a commit fbe4237

File tree

5 files changed

+127
-69
lines changed

5 files changed

+127
-69
lines changed

compiler/src/dotty/tools/dotc/typer/Inferencing.scala

Lines changed: 87 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -222,72 +222,6 @@ object Inferencing {
222222
case _ => NoType
223223
}
224224

225-
/** Interpolate those undetermined type variables in the widened type of this tree
226-
* which are introduced by type application contained in the tree.
227-
* If such a variable appears covariantly in type `tp` or does not appear at all,
228-
* approximate it by its lower bound. Otherwise, if it appears contravariantly
229-
* in type `tp` approximate it by its upper bound.
230-
* @param ownedBy if it is different from NoSymbol, all type variables owned by
231-
* `ownedBy` qualify, independent of position.
232-
* Without that second condition, it can be that certain variables escape
233-
* interpolation, for instance when their tree was eta-lifted, so
234-
* the typechecked tree is no longer the tree in which the variable
235-
* was declared. A concrete example of this phenomenon can be
236-
* observed when compiling core.TypeOps#asSeenFrom.
237-
*/
238-
def interpolateUndetVars(tree: Tree, ownedBy: Symbol)(implicit ctx: Context): Unit = {
239-
val constraint = ctx.typerState.constraint
240-
val qualifies = (tvar: TypeVar) =>
241-
(tree contains tvar.bindingTree) || ownedBy.exists && tvar.owner == ownedBy
242-
def interpolate() = Stats.track("interpolateUndetVars") {
243-
val tp = tree.tpe.widen
244-
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}")}")
245-
constr.println(s"qualifying undet vars: ${constraint.uninstVars filter qualifies map (tvar => s"$tvar / ${tvar.show}")}, constraint: ${constraint.show}")
246-
247-
val vs = variances(tp, qualifies)
248-
val hasUnreportedErrors = ctx.typerState.reporter match {
249-
case r: StoreReporter if r.hasErrors => true
250-
case _ => false
251-
}
252-
// Avoid interpolating variables if typerstate has unreported errors.
253-
// Reason: The errors might reflect unsatisfiable constraints. In that
254-
// case interpolating without taking account the constraints risks producing
255-
// nonsensical types that then in turn produce incomprehensible errors.
256-
// An example is in neg/i1240.scala. Without the condition in the next code line
257-
// we get for
258-
//
259-
// val y: List[List[String]] = List(List(1))
260-
//
261-
// i1430.scala:5: error: type mismatch:
262-
// found : Int(1)
263-
// required: Nothing
264-
// val y: List[List[String]] = List(List(1))
265-
// ^
266-
// With the condition, we get the much more sensical:
267-
//
268-
// i1430.scala:5: error: type mismatch:
269-
// found : Int(1)
270-
// required: String
271-
// val y: List[List[String]] = List(List(1))
272-
if (!hasUnreportedErrors)
273-
vs foreachBinding { (tvar, v) =>
274-
if (v != 0 && ctx.typerState.constraint.contains(tvar)) {
275-
// previous interpolations could have already instantiated `tvar`
276-
// through unification, that's why we have to check again whether `tvar`
277-
// is contained in the current constraint.
278-
typr.println(s"interpolate ${if (v == 1) "co" else "contra"}variant ${tvar.show} in ${tp.show}")
279-
tvar.instantiate(fromBelow = v == 1)
280-
}
281-
}
282-
for (tvar <- constraint.uninstVars)
283-
if (!(vs contains tvar) && qualifies(tvar)) {
284-
typr.println(s"instantiating non-occurring ${tvar.show} in ${tp.show} / $tp")
285-
tvar.instantiate(fromBelow = true)
286-
}
287-
}
288-
if (constraint.uninstVars exists qualifies) interpolate()
289-
}
290-
291225
/** Instantiate undetermined type variables to that type `tp` is
292226
* maximized and return None. If this is not possible, because a non-variant
293227
* typevar is not uniquely determined, return that typevar in a Some.
@@ -375,6 +309,93 @@ object Inferencing {
375309
}
376310
}
377311

312+
trait Inferencing { this: Typer =>
313+
import Inferencing._
314+
import tpd._
315+
316+
/** Interpolate those undetermined type variables in the widened type of this tree
317+
* which are introduced by type application contained in the tree.
318+
* If such a variable appears covariantly in type `tp` or does not appear at all,
319+
* approximate it by its lower bound. Otherwise, if it appears contravariantly
320+
* in type `tp` approximate it by its upper bound.
321+
* @param ownedBy if it is different from NoSymbol, all type variables owned by
322+
* `ownedBy` qualify, independent of position.
323+
* Without that second condition, it can be that certain variables escape
324+
* interpolation, for instance when their tree was eta-lifted, so
325+
* the typechecked tree is no longer the tree in which the variable
326+
* was declared. A concrete example of this phenomenon can be
327+
* observed when compiling core.TypeOps#asSeenFrom.
328+
*/
329+
def interpolateUndetVars(tree: Tree, ownedBy: Symbol, pt: Type)(implicit ctx: Context): Unit = {
330+
val constraint = ctx.typerState.constraint
331+
val qualifies = (tvar: TypeVar) =>
332+
(tree contains tvar.bindingTree) || ownedBy.exists && tvar.owner == ownedBy
333+
def interpolate() = Stats.track("interpolateUndetVars") {
334+
val tp = tree.tpe.widen
335+
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}")}")
336+
constr.println(s"qualifying undet vars: ${constraint.uninstVars filter qualifies map (tvar => s"$tvar / ${tvar.show}")}, constraint: ${constraint.show}")
337+
338+
val vs = variances(tp, qualifies)
339+
val hasUnreportedErrors = ctx.typerState.reporter match {
340+
case r: StoreReporter if r.hasErrors => true
341+
case _ => false
342+
}
343+
344+
var isConstrained = false
345+
def ensureConstrained() =
346+
if (!isConstrained) {
347+
isConstrained = true
348+
tree match {
349+
case tree: Apply => // already constrained
350+
case _ => tree.tpe match {
351+
case _: MethodOrPoly => // already constrained
352+
case tp => constrainResult(tp, pt)
353+
}
354+
}
355+
}
356+
357+
// Avoid interpolating variables if typerstate has unreported errors.
358+
// Reason: The errors might reflect unsatisfiable constraints. In that
359+
// case interpolating without taking account the constraints risks producing
360+
// nonsensical types that then in turn produce incomprehensible errors.
361+
// An example is in neg/i1240.scala. Without the condition in the next code line
362+
// we get for
363+
//
364+
// val y: List[List[String]] = List(List(1))
365+
//
366+
// i1430.scala:5: error: type mismatch:
367+
// found : Int(1)
368+
// required: Nothing
369+
// val y: List[List[String]] = List(List(1))
370+
// ^
371+
// With the condition, we get the much more sensical:
372+
//
373+
// i1430.scala:5: error: type mismatch:
374+
// found : Int(1)
375+
// required: String
376+
// val y: List[List[String]] = List(List(1))
377+
if (!hasUnreportedErrors)
378+
vs foreachBinding { (tvar, v) =>
379+
if (v != 0 && ctx.typerState.constraint.contains(tvar)) {
380+
// previous interpolations could have already instantiated `tvar`
381+
// through unification, that's why we have to check again whether `tvar`
382+
// is contained in the current constraint.
383+
typr.println(s"interpolate ${if (v == 1) "co" else "contra"}variant ${tvar.show} in ${tp.show}")
384+
ensureConstrained()
385+
tvar.instantiate(fromBelow = v == 1)
386+
}
387+
}
388+
for (tvar <- constraint.uninstVars)
389+
if (!(vs contains tvar) && qualifies(tvar)) {
390+
typr.println(s"instantiating non-occurring ${tvar.show} in ${tp.show} / $tp")
391+
ensureConstrained()
392+
tvar.instantiate(fromBelow = true)
393+
}
394+
}
395+
if (constraint.uninstVars exists qualifies) interpolate()
396+
}
397+
}
398+
378399
/** An enumeration controlling the degree of forcing in "is-dully-defined" checks. */
379400
@sharable object ForceDegree {
380401
class Value(val appliesTo: TypeVar => Boolean, val minimizeAll: Boolean)

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,14 @@ object Typer {
7979
private val DroppedEmptyArgs = new Property.Key[Unit]
8080
}
8181

82-
class Typer extends Namer with TypeAssigner with Applications with Implicits with Dynamic with Checking with Docstrings {
82+
class Typer extends Namer
83+
with TypeAssigner
84+
with Applications
85+
with Implicits
86+
with Inferencing
87+
with Dynamic
88+
with Checking
89+
with Docstrings {
8390

8491
import Typer._
8592
import tpd.{cpy => _, _}
@@ -1945,8 +1952,8 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
19451952
/*>|>*/ trace(i"adapting $tree of type ${tree.tpe} to $pt", typr, show = true) /*<|<*/ {
19461953
if (!tree.denot.isOverloaded) {
19471954
// for overloaded trees: resolve overloading before simplifying
1948-
if (tree.isDef) interpolateUndetVars(tree, tree.symbol)
1949-
else if (!tree.tpe.widen.isInstanceOf[LambdaType]) interpolateUndetVars(tree, NoSymbol)
1955+
if (tree.isDef) interpolateUndetVars(tree, tree.symbol, pt)
1956+
else if (!tree.tpe.widen.isInstanceOf[LambdaType]) interpolateUndetVars(tree, NoSymbol, pt)
19501957
tree.overwriteType(tree.tpe.simplified)
19511958
}
19521959
adaptInterpolated(tree, pt)

tests/neg/i2997.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
case class Foo[T <: Int with Singleton](t : T)
2+
3+
object Test {
4+
val one = 1
5+
final val final_one = 1
6+
val a : 1 = Foo(1).t
7+
val b : Int = Foo(one).t // error: does not conform to upper bound Int & Singleton
8+
val c : 1 = Foo(final_one).t
9+
}

tests/pos/i2997.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
case class Foo[T <: Int with Singleton](t : T)
2+
3+
object Test {
4+
val one = 1
5+
final val final_one = 1
6+
val a : 1 = Foo(1).t
7+
val c : 1 = Foo(final_one).t
8+
}

tests/pos/i2998.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
class Foo[T] {
2+
def zero: T = ???
3+
def one(): T = ???
4+
val two: T = ???
5+
}
6+
7+
object Test {
8+
def foo[T](x: T): Foo[T] = new Foo
9+
10+
val b: 1 = foo(1).one() // OK
11+
val a: 1 = foo(1).zero // Fails: Found: Int, required: Int(1)
12+
val c: 1 = foo(1).two // Fails: Found: Int, required: Int(1)
13+
}

0 commit comments

Comments
 (0)