Skip to content

Commit 0182e06

Browse files
committed
Fix improper usage of constrained breaking type inference
In multiple places, we had code equivalent to the following pattern: val (tl2, targs) = constrained(tl) tl2.resultType <:< ... which lead to subtype checks directly involving the TypeParamRefs of the constrained type lambda. This commit uses the following pattern instead: val (tl2, targs) = constrained(tl) tl2.instantiate(targs.map(_.tpe)) <:< ... which substitutes the TypeParamRefs by the corresponding TypeVars in the constraint. This is necessary because when comparing TypeParamRefs in isSubType: - we only recurse on the bounds of the TypeParamRef using `isSubTypeWhenFrozen` which prevents further constraints from being added (see the added stm.scala test case for an example where this matters). - if the corresponding TypeVar is instantiated and the TyperState has been gc()'ed, there is no way to find the instantiation corresponding to the current TypeParamRef anymore. There is one place where I left the old logic intact: `TrackingTypeComparer#matchCase` because the match type caching logic (in `MatchType#reduced`) conflicted with the use of TypeVars since it retracts the current TyperState. This change breaks a test which involves an unlikely combination of implicit conversion, overloading and apply insertion. Given that there is always a tension between type inference and implicit conversion, and that we're discouraging uses of implicit conversions, I think that's an acceptable trade-off.
1 parent faf9538 commit 0182e06

File tree

6 files changed

+27
-13
lines changed

6 files changed

+27
-13
lines changed

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,7 @@ trait Applications extends Compatibility {
411411
*/
412412
@threadUnsafe lazy val methType: Type = liftedFunType.widen match {
413413
case funType: MethodType => funType
414-
case funType: PolyType => constrained(funType).resultType
414+
case funType: PolyType => instantiateWithTypeVars(funType)
415415
case tp => tp //was: funType
416416
}
417417

@@ -1571,7 +1571,7 @@ trait Applications extends Compatibility {
15711571
case tp2: MethodType => true // (3a)
15721572
case tp2: PolyType if tp2.resultType.isInstanceOf[MethodType] => true // (3a)
15731573
case tp2: PolyType => // (3b)
1574-
explore(isAsSpecificValueType(tp1, constrained(tp2).resultType))
1574+
explore(isAsSpecificValueType(tp1, instantiateWithTypeVars(tp2)))
15751575
case _ => // 3b)
15761576
isAsSpecificValueType(tp1, tp2)
15771577
}
@@ -1738,7 +1738,7 @@ trait Applications extends Compatibility {
17381738
resultType.revealIgnored match {
17391739
case resultType: ValueType =>
17401740
altType.widen match {
1741-
case tp: PolyType => resultConforms(altSym, constrained(tp).resultType, resultType)
1741+
case tp: PolyType => resultConforms(altSym, instantiateWithTypeVars(tp), resultType)
17421742
case tp: MethodType => constrainResult(altSym, tp.resultType, resultType)
17431743
case _ => true
17441744
}

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -659,6 +659,11 @@ object ProtoTypes {
659659
def constrained(tl: TypeLambda)(using Context): TypeLambda =
660660
constrained(tl, EmptyTree)._1
661661

662+
/** Instantiate `tl` with fresh type variables added to the constraint. */
663+
def instantiateWithTypeVars(tl: TypeLambda)(using Context): Type =
664+
val targs = constrained(tl, ast.tpd.EmptyTree, alwaysAddTypeVars = true)._2
665+
tl.instantiate(targs.tpes)
666+
662667
/** A new type variable with given bounds for its origin.
663668
* @param represents If exists, the TermParamRef that the TypeVar represents
664669
* in the substitution generated by `resultTypeApprox`
@@ -707,7 +712,7 @@ object ProtoTypes {
707712
else mt.resultType
708713

709714
/** The normalized form of a type
710-
* - unwraps polymorphic types, tracking their parameters in the current constraint
715+
* - instantiate polymorphic types with fresh type variables in the current constraint
711716
* - skips implicit parameters of methods and functions;
712717
* if result type depends on implicit parameter, replace with wildcard.
713718
* - converts non-dependent method types to the corresponding function types
@@ -726,7 +731,7 @@ object ProtoTypes {
726731
Stats.record("normalize")
727732
tp.widenSingleton match {
728733
case poly: PolyType =>
729-
normalize(constrained(poly).resultType, pt)
734+
normalize(instantiateWithTypeVars(poly), pt)
730735
case mt: MethodType =>
731736
if (mt.isImplicitMethod) normalize(resultTypeApprox(mt, wildcardOnly = true), pt)
732737
else if (mt.isResultDependent) tp

compiler/test/dotty/tools/dotc/core/ConstraintsTest.scala

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import dotty.tools.dotc.core.Contexts.{*, given}
77
import dotty.tools.dotc.core.Decorators.{*, given}
88
import dotty.tools.dotc.core.Symbols.*
99
import dotty.tools.dotc.core.Types.*
10+
import dotty.tools.dotc.ast.tpd.*
1011
import dotty.tools.dotc.typer.ProtoTypes.constrained
1112

1213
import org.junit.Test
@@ -18,8 +19,8 @@ class ConstraintsTest:
1819
@Test def mergeParamsTransitivity: Unit =
1920
inCompilerContext(TestConfiguration.basicClasspath,
2021
scalaSources = "trait A { def foo[S, T, R]: Any }") {
21-
val tp = constrained(requiredClass("A").typeRef.select("foo".toTermName).info.asInstanceOf[TypeLambda])
22-
val List(s, t, r) = tp.paramRefs
22+
val tvars = constrained(requiredClass("A").typeRef.select("foo".toTermName).info.asInstanceOf[TypeLambda], EmptyTree, alwaysAddTypeVars = true)._2
23+
val List(s, t, r) = tvars.tpes
2324

2425
val innerCtx = ctx.fresh.setExploreTyperState()
2526
inContext(innerCtx) {
@@ -37,8 +38,8 @@ class ConstraintsTest:
3738
@Test def mergeBoundsTransitivity: Unit =
3839
inCompilerContext(TestConfiguration.basicClasspath,
3940
scalaSources = "trait A { def foo[S, T]: Any }") {
40-
val tp = constrained(requiredClass("A").typeRef.select("foo".toTermName).info.asInstanceOf[TypeLambda])
41-
val List(s, t) = tp.paramRefs
41+
val tvars = constrained(requiredClass("A").typeRef.select("foo".toTermName).info.asInstanceOf[TypeLambda], EmptyTree, alwaysAddTypeVars = true)._2
42+
val List(s, t) = tvars.tpes
4243

4344
val innerCtx = ctx.fresh.setExploreTyperState()
4445
inContext(innerCtx) {

tests/pos/stm.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
class Inv[X]
2+
class Ref[X]
3+
object Ref {
4+
def apply(i: Inv[Int], x: Int): Ref[Int] = ???
5+
def apply[Y](i: Inv[Y], x: Y): Ref[Y] = ???
6+
}
7+
8+
class A {
9+
val ref: Ref[List[AnyRef]] = Ref(new Inv[List[AnyRef]], List.empty)
10+
}

tests/pos/t0851.scala

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
package test
22

33
object test1 {
4-
case class Foo[T,T2](f : (T,T2) => String) extends (((T,T2)) => String){
4+
case class Foo[T,T2](f : (T,T2) => String) {
55
def apply(t : T) = (s:T2) => f(t,s)
6-
def apply(p : (T,T2)) = f(p._1,p._2)
76
}
87
implicit def g[T](f : (T,String) => String): Foo[T, String] = Foo(f)
98
def main(args : Array[String]) : Unit = {

tests/pos/t2913.scala

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,8 @@ object TestNoAutoTupling {
3333

3434
// t0851 is essentially the same:
3535
object test1 {
36-
case class Foo[T,T2](f : (T,T2) => String) extends (((T,T2)) => String){
36+
case class Foo[T,T2](f : (T,T2) => String) {
3737
def apply(t : T) = (s:T2) => f(t,s)
38-
def apply(p : (T,T2)) = f(p._1,p._2)
3938
}
4039
implicit def g[T](f : (T,String) => String): test1.Foo[T,String] = Foo(f)
4140
def main(args : Array[String]) : Unit = {

0 commit comments

Comments
 (0)