From ff8659e79351fd8678444eb31ac4b80a915036a5 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 3 Jun 2021 17:28:05 +0200 Subject: [PATCH 01/14] Don't allow wildcard types in constraints Fixes #12677 --- .../tools/dotc/core/ConstraintHandling.scala | 10 ++++-- tests/pos/i12677.scala | 31 +++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 tests/pos/i12677.scala diff --git a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala index e9ca12e3db9e..e8d2bba934b8 100644 --- a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -97,15 +97,21 @@ trait ConstraintHandling { constraint = constraint.replace(param, bound) true else + val dropWildcards = new ApproximatingTypeMap: + if !isUpper then variance = -1 + def apply(t: Type): Type = t match + case WildcardType => range(param.underlying.loBound, param.underlying.hiBound) + case _ => mapOver(t) // Narrow one of the bounds of type parameter `param` // If `isUpper` is true, ensure that `param <: `bound`, otherwise ensure // that `param >: bound`. + val bound1 = dropWildcards(bound) val narrowedBounds = val saved = homogenizeArgs homogenizeArgs = Config.alignArgsInAnd try - if isUpper then oldBounds.derivedTypeBounds(lo, hi & bound) - else oldBounds.derivedTypeBounds(lo | bound, hi) + if isUpper then oldBounds.derivedTypeBounds(lo, hi & bound1) + else oldBounds.derivedTypeBounds(lo | bound1, hi) finally homogenizeArgs = saved val c1 = constraint.updateEntry(param, narrowedBounds) (c1 eq constraint) diff --git a/tests/pos/i12677.scala b/tests/pos/i12677.scala new file mode 100644 index 000000000000..339eb5594b34 --- /dev/null +++ b/tests/pos/i12677.scala @@ -0,0 +1,31 @@ +class F[A] +object F { + def apply[A](a: => A) = new F[A] +} + +trait TC[A] { type Out } +object TC { + implicit def tc[A]: TC[A] { type Out = String } = ??? +} + +// ==================================================================================== +object Bug { + final class CustomHook[A] { + def blah(implicit tc: TC[A]): CustomHook[tc.Out] = ??? + } + + def i: CustomHook[Int] = ??? + val f = F(i.blah) + f: F[CustomHook[String]] // error +} + +// ==================================================================================== +object Workaround { + final class CustomHook[A] { + def blah[B](implicit tc: TC[A] { type Out = B }): CustomHook[B] = ??? // raise type + } + + def i: CustomHook[Int] = ??? + val f = F(i.blah) + f: F[CustomHook[String]] // works +} \ No newline at end of file From 1950246cdf755e8fdf49976ee8a082ebe761ac6d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 4 Jun 2021 12:52:42 +0200 Subject: [PATCH 02/14] Only drop wildcards for necesessary comparisons Wildcards in constraints essentially mean that the constrained variable is a sub/supertype of _every_ instance of the bound. For useNecesaryEither, this is wrong. Approximate the bound not to use wildcards instead. --- .../src/dotty/tools/dotc/core/ConstraintHandling.scala | 8 ++++++-- compiler/src/dotty/tools/dotc/core/TypeComparer.scala | 2 ++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala index e8d2bba934b8..0c981f1b03e5 100644 --- a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -78,6 +78,8 @@ trait ConstraintHandling { def fullBounds(param: TypeParamRef)(using Context): TypeBounds = nonParamBounds(param).derivedTypeBounds(fullLowerBound(param), fullUpperBound(param)) + protected def allowWildcards: Boolean = true + protected def addOneBound(param: TypeParamRef, bound: Type, isUpper: Boolean)(using Context): Boolean = if !constraint.contains(param) then true else if !isUpper && param.occursIn(bound) then @@ -100,8 +102,10 @@ trait ConstraintHandling { val dropWildcards = new ApproximatingTypeMap: if !isUpper then variance = -1 def apply(t: Type): Type = t match - case WildcardType => range(param.underlying.loBound, param.underlying.hiBound) - case _ => mapOver(t) + case WildcardType if !allowWildcards => + range(param.underlying.loBound, param.underlying.hiBound) + case _ => + mapOver(t) // Narrow one of the bounds of type parameter `param` // If `isUpper` is true, ensure that `param <: `bound`, otherwise ensure // that `param >: bound`. diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 20ff3dff6b5a..d063acc6fd93 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -139,6 +139,8 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling try topLevelSubType(tp1, tp2) finally useNecessaryEither = saved + override protected def allowWildcards: Boolean = !useNecessaryEither + def testSubType(tp1: Type, tp2: Type): CompareResult = GADTused = false if !topLevelSubType(tp1, tp2) then CompareResult.Fail From 87372540fbc095af48f86a81d49f38428bd6989c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 4 Jun 2021 13:06:38 +0200 Subject: [PATCH 03/14] drop all kinds of WildcardType under useNecessaryEither --- compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala index 0c981f1b03e5..72c33a45fd28 100644 --- a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -102,8 +102,10 @@ trait ConstraintHandling { val dropWildcards = new ApproximatingTypeMap: if !isUpper then variance = -1 def apply(t: Type): Type = t match - case WildcardType if !allowWildcards => - range(param.underlying.loBound, param.underlying.hiBound) + case t: WildcardType if !allowWildcards => + t.optBounds match + case TypeBounds(lo, hi) => range(lo, hi) + case _ => range(defn.NothingType, defn.AnyType) case _ => mapOver(t) // Narrow one of the bounds of type parameter `param` From 0cdbff17abeb27339d62dadabb8217fb626449c8 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 4 Jun 2021 15:35:18 +0200 Subject: [PATCH 04/14] better bounds for wildcards in resultTypeApprox --- compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index eff4a8cb9c94..2725aa19054b 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -707,8 +707,10 @@ object ProtoTypes { if wildcardOnly || ctx.mode.is(Mode.TypevarsMissContext) || !ref.underlying.widenExpr.isValueTypeOrWildcard - then WildcardType - else newDepTypeVar(ref) + then + WildcardType(ref.underlying.substParams(mt, mt.paramRefs.map(_ => WildcardType)).toBounds) + else + newDepTypeVar(ref) mt.resultType.substParams(mt, mt.paramRefs.map(replacement)) else mt.resultType From 5807feffb15faa424017ea0ea4057656bd3abdf1 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 4 Jun 2021 18:31:14 +0200 Subject: [PATCH 05/14] Avoid wildcard types in constraints Ther semantics is fishy. Instead, use avoidance in necessarySubType and in TypeVarsMiss context mode, and use fresh type variables elsehwere. --- .../dotty/tools/dotc/core/ConstraintHandling.scala | 11 +++++++---- compiler/src/dotty/tools/dotc/core/Types.scala | 5 +++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala index 72c33a45fd28..c6e6de8be0fe 100644 --- a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -10,6 +10,7 @@ import Flags._ import config.Config import config.Printers.typr import reporting.trace +import typer.ProtoTypes.newTypeVar import StdNames.tpnme /** Methods for adding constraints and solving them. @@ -102,10 +103,12 @@ trait ConstraintHandling { val dropWildcards = new ApproximatingTypeMap: if !isUpper then variance = -1 def apply(t: Type): Type = t match - case t: WildcardType if !allowWildcards => - t.optBounds match - case TypeBounds(lo, hi) => range(lo, hi) - case _ => range(defn.NothingType, defn.AnyType) + case t: WildcardType => + if !allowWildcards || ctx.mode.is(Mode.TypevarsMissContext) then + val bounds = t.effectiveBounds + range(bounds.lo, bounds.hi) + else + newTypeVar(t.effectiveBounds) case _ => mapOver(t) // Narrow one of the bounds of type parameter `param` diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 937fdd2e1c43..a5dd1fbac435 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -5053,6 +5053,11 @@ object Types { /** Wildcard type, possibly with bounds */ abstract case class WildcardType(optBounds: Type) extends CachedGroundType with TermType { + + def effectiveBounds(using Context): TypeBounds = optBounds match + case bounds: TypeBounds => bounds + case _ => TypeBounds.empty + def derivedWildcardType(optBounds: Type)(using Context): WildcardType = if (optBounds eq this.optBounds) this else if (!optBounds.exists) WildcardType From 7020e7835b82770a495b313bf497216ec8b5bbe2 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 4 Jun 2021 19:09:51 +0200 Subject: [PATCH 06/14] Cleanup addOneBound logic --- .../tools/dotc/core/ConstraintHandling.scala | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala index c6e6de8be0fe..cdebed3adcbd 100644 --- a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -79,42 +79,42 @@ trait ConstraintHandling { def fullBounds(param: TypeParamRef)(using Context): TypeBounds = nonParamBounds(param).derivedTypeBounds(fullLowerBound(param), fullUpperBound(param)) - protected def allowWildcards: Boolean = true + /** If true: eliminate wildcards in bounds by avoidance. + * Otherwise replace them by fresh variables, except that + * in mode TypeVarsMissContext, wildcards are always eliminated by approximation. + */ + protected def approximateWildcards: Boolean = true - protected def addOneBound(param: TypeParamRef, bound: Type, isUpper: Boolean)(using Context): Boolean = + protected def addOneBound(param: TypeParamRef, bound0: Type, isUpper: Boolean)(using Context): Boolean = if !constraint.contains(param) then true - else if !isUpper && param.occursIn(bound) then + else if !isUpper && param.occursIn(bound0) then // We don't allow recursive lower bounds when defining a type, // so we shouldn't allow them as constraints either. false else + val dropWildcards = new ApproximatingTypeMap: + if !isUpper then variance = -1 + def apply(t: Type): Type = t match + case t: WildcardType => + if approximateWildcards || ctx.mode.is(Mode.TypevarsMissContext) then + val bounds = t.effectiveBounds + range(bounds.lo, bounds.hi) + else + newTypeVar(t.effectiveBounds) + case _ => + mapOver(t) + val bound1 = dropWildcards(bound0) val oldBounds @ TypeBounds(lo, hi) = constraint.nonParamBounds(param) - val equalBounds = (if isUpper then lo else hi) eq bound - if equalBounds - && !bound.existsPart(bp => bp.isInstanceOf[WildcardType] || (bp eq param)) - then - // The narrowed bounds are equal and do not contain wildcards, + val equalBounds = (if isUpper then lo else hi) eq bound1 + if equalBounds && !bound1.existsPart(_ eq param, stopAtStatic = true) then + // The narrowed bounds are equal and not recursive, // so we can remove `param` from the constraint. - // (Handling wildcards requires choosing a bound, but we don't know which - // bound to choose here, this is handled in `ConstraintHandling#approximation`) - constraint = constraint.replace(param, bound) + constraint = constraint.replace(param, bound1) true else - val dropWildcards = new ApproximatingTypeMap: - if !isUpper then variance = -1 - def apply(t: Type): Type = t match - case t: WildcardType => - if !allowWildcards || ctx.mode.is(Mode.TypevarsMissContext) then - val bounds = t.effectiveBounds - range(bounds.lo, bounds.hi) - else - newTypeVar(t.effectiveBounds) - case _ => - mapOver(t) // Narrow one of the bounds of type parameter `param` // If `isUpper` is true, ensure that `param <: `bound`, otherwise ensure // that `param >: bound`. - val bound1 = dropWildcards(bound) val narrowedBounds = val saved = homogenizeArgs homogenizeArgs = Config.alignArgsInAnd From be25c21fe0f61442f71f7fa97bb2d000f5e22b39 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 4 Jun 2021 19:10:38 +0200 Subject: [PATCH 07/14] Verify that constraints do not contain wildcard types --- compiler/src/dotty/tools/dotc/config/Config.scala | 3 +++ compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala | 2 ++ compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala | 1 + compiler/src/dotty/tools/dotc/core/TypeComparer.scala | 2 +- compiler/src/dotty/tools/dotc/core/Types.scala | 2 +- 5 files changed, 8 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/config/Config.scala b/compiler/src/dotty/tools/dotc/config/Config.scala index 32470a026084..e61b54fb1146 100644 --- a/compiler/src/dotty/tools/dotc/config/Config.scala +++ b/compiler/src/dotty/tools/dotc/config/Config.scala @@ -34,6 +34,9 @@ object Config { */ inline val checkConstraintsPropagated = false + /** Check that constraint bounds do not contain wildcard types */ + inline val checkNoWildcardsInConstraint = true + /** If a constraint is over a type lambda `tl` and `tvar` is one of * the type variables associated with `tl` in the constraint, check * that the origin of `tvar` is a parameter of `tl`. diff --git a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala index cdebed3adcbd..055e7256712e 100644 --- a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -270,6 +270,7 @@ trait ConstraintHandling { var trackedPolis: List[PolyType] = Nil def apply(tp: Type) = tp match case tp: WildcardType => + assert(false) val poly = PolyType(tpnme.EMPTY :: Nil)(pt => tp.bounds :: Nil, pt => defn.AnyType) trackedPolis = poly :: trackedPolis poly.paramRefs.head @@ -309,6 +310,7 @@ trait ConstraintHandling { def apply(tp: Type) = mapOver { tp match case tp: WildcardType => + assert(false) pickOneBound(tp.bounds) case tp: TypeParamRef if substWildcards.trackedPolis.contains(tp.binder) => pickOneBound(fullBounds(tp)) diff --git a/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala b/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala index 406ec58b9846..7e6045c8610c 100644 --- a/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala +++ b/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala @@ -376,6 +376,7 @@ class OrderingConstraint(private val boundsMap: ParamBounds, Nil private def updateEntry(current: This, param: TypeParamRef, tp: Type)(using Context): This = { + if Config.checkNoWildcardsInConstraint then assert(!tp.containsWildcardTypes) var current1 = boundsLens.update(this, current, param, tp) tp match { case TypeBounds(lo, hi) => diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index d063acc6fd93..1482e472c71c 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -139,7 +139,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling try topLevelSubType(tp1, tp2) finally useNecessaryEither = saved - override protected def allowWildcards: Boolean = !useNecessaryEither + override protected def approximateWildcards: Boolean = useNecessaryEither def testSubType(tp1: Type, tp2: Type): CompareResult = GADTused = false diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index a5dd1fbac435..71ef03c0f24d 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -439,7 +439,7 @@ object Types { /** Does this type contain wildcard types? */ final def containsWildcardTypes(using Context) = - existsPart(_.isInstanceOf[WildcardType], stopAtStatic = true) + existsPart(_.isInstanceOf[WildcardType], stopAtStatic = true, forceLazy = false) // ----- Higher-order combinators ----------------------------------- From cfbe9e0c64ea09826dd5e26a01f9d98bebc78add Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 4 Jun 2021 19:22:36 +0200 Subject: [PATCH 08/14] Cleanups --- .../src/dotty/tools/dotc/config/Config.scala | 2 +- .../tools/dotc/core/ConstraintHandling.scala | 94 +++---------------- 2 files changed, 12 insertions(+), 84 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/config/Config.scala b/compiler/src/dotty/tools/dotc/config/Config.scala index e61b54fb1146..da2755b76423 100644 --- a/compiler/src/dotty/tools/dotc/config/Config.scala +++ b/compiler/src/dotty/tools/dotc/config/Config.scala @@ -35,7 +35,7 @@ object Config { inline val checkConstraintsPropagated = false /** Check that constraint bounds do not contain wildcard types */ - inline val checkNoWildcardsInConstraint = true + inline val checkNoWildcardsInConstraint = false /** If a constraint is over a type lambda `tl` and `tvar` is one of * the type variables associated with `tl` in the constraint, check diff --git a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala index 055e7256712e..6360ebf8048f 100644 --- a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -85,9 +85,9 @@ trait ConstraintHandling { */ protected def approximateWildcards: Boolean = true - protected def addOneBound(param: TypeParamRef, bound0: Type, isUpper: Boolean)(using Context): Boolean = + protected def addOneBound(param: TypeParamRef, rawBound: Type, isUpper: Boolean)(using Context): Boolean = if !constraint.contains(param) then true - else if !isUpper && param.occursIn(bound0) then + else if !isUpper && param.occursIn(rawBound) then // We don't allow recursive lower bounds when defining a type, // so we shouldn't allow them as constraints either. false @@ -100,16 +100,16 @@ trait ConstraintHandling { val bounds = t.effectiveBounds range(bounds.lo, bounds.hi) else - newTypeVar(t.effectiveBounds) + newTypeVar(apply(t.effectiveBounds).toBounds) case _ => mapOver(t) - val bound1 = dropWildcards(bound0) + val bound = dropWildcards(rawBound) val oldBounds @ TypeBounds(lo, hi) = constraint.nonParamBounds(param) - val equalBounds = (if isUpper then lo else hi) eq bound1 - if equalBounds && !bound1.existsPart(_ eq param, stopAtStatic = true) then + val equalBounds = (if isUpper then lo else hi) eq bound + if equalBounds && !bound.existsPart(_ eq param, stopAtStatic = true) then // The narrowed bounds are equal and not recursive, // so we can remove `param` from the constraint. - constraint = constraint.replace(param, bound1) + constraint = constraint.replace(param, bound) true else // Narrow one of the bounds of type parameter `param` @@ -119,8 +119,8 @@ trait ConstraintHandling { val saved = homogenizeArgs homogenizeArgs = Config.alignArgsInAnd try - if isUpper then oldBounds.derivedTypeBounds(lo, hi & bound1) - else oldBounds.derivedTypeBounds(lo | bound1, hi) + if isUpper then oldBounds.derivedTypeBounds(lo, hi & bound) + else oldBounds.derivedTypeBounds(lo | bound, hi) finally homogenizeArgs = saved val c1 = constraint.updateEntry(param, narrowedBounds) (c1 eq constraint) @@ -260,83 +260,11 @@ trait ConstraintHandling { * @pre `param` is in the constraint's domain. */ final def approximation(param: TypeParamRef, fromBelow: Boolean)(using Context): Type = - - /** Substitute wildcards with fresh TypeParamRefs, to be compared with - * other bound, so that they can be instantiated. - */ - object substWildcards extends TypeMap: - override def stopAtStatic = true - - var trackedPolis: List[PolyType] = Nil - def apply(tp: Type) = tp match - case tp: WildcardType => - assert(false) - val poly = PolyType(tpnme.EMPTY :: Nil)(pt => tp.bounds :: Nil, pt => defn.AnyType) - trackedPolis = poly :: trackedPolis - poly.paramRefs.head - case _ => - mapOver(tp) - end substWildcards - - /** Replace TypeParamRefs substituted for wildcards by `substWildCards` - * and any remaining wildcards by a safe approximation - */ - val replaceWildcards = new TypeMap: - override def stopAtStatic = true - - /** Try to instantiate a wildcard or TypeParamRef representing a wildcard - * to a type that is known to conform to it. - * This means: - * If fromBelow is true, we minimize the type overall - * Hence, if variance < 0, pick the maximal safe type: bounds.lo - * (i.e. the whole bounds range is over the type). - * If variance > 0, pick the minimal safe type: bounds.hi - * (i.e. the whole bounds range is under the type). - * If variance == 0, pick bounds.lo anyway (this is arbitrary but in line with - * the principle that we pick the smaller type when in doubt). - * If fromBelow is false, we maximize the type overall and reverse the bounds - * If variance != 0. For variance == 0, we still minimize. - * In summary we pick the bound given by this table: - * - * variance | -1 0 1 - * ------------------------ - * from below | lo lo hi - * from above | hi lo lo - */ - def pickOneBound(bounds: TypeBounds) = - if variance == 0 || fromBelow == (variance < 0) then bounds.lo - else bounds.hi - - def apply(tp: Type) = mapOver { - tp match - case tp: WildcardType => - assert(false) - pickOneBound(tp.bounds) - case tp: TypeParamRef if substWildcards.trackedPolis.contains(tp.binder) => - pickOneBound(fullBounds(tp)) - case _ => tp - } - end replaceWildcards - constraint.entry(param) match case entry: TypeBounds => val useLowerBound = fromBelow || param.occursIn(entry.hi) - val rawBound = if useLowerBound then fullLowerBound(param) else fullUpperBound(param) - val bound = substWildcards(rawBound) - val inst = - if bound eq rawBound then bound - else - // Get rid of wildcards by mapping them to fresh TypeParamRefs - // with constraints derived from comparing both bounds, and then - // instantiating. See pos/i10161.scala for a test where this matters. - val saved = constraint - try - for poly <- substWildcards.trackedPolis do addToConstraint(poly, Nil) - if useLowerBound then bound <:< fullUpperBound(param) - else fullLowerBound(param) <:< bound - replaceWildcards(bound) - finally constraint = saved - typr.println(s"approx ${param.show}, from below = $fromBelow, bound = ${bound.show}, inst = ${inst.show}") + val inst = if useLowerBound then fullLowerBound(param) else fullUpperBound(param) + typr.println(s"approx ${param.show}, from below = $fromBelow, inst = ${inst.show}") inst case inst => assert(inst.exists, i"param = $param\nconstraint = $constraint") From 802c5d7cf55e574324412c21aefb370eb7d6057d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 4 Jun 2021 19:31:42 +0200 Subject: [PATCH 09/14] Streamline approximateWildcards logic --- .../src/dotty/tools/dotc/core/ConstraintHandling.scala | 7 +++---- compiler/src/dotty/tools/dotc/core/TypeComparer.scala | 9 ++++++++- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala index 6360ebf8048f..f10796975941 100644 --- a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -79,9 +79,8 @@ trait ConstraintHandling { def fullBounds(param: TypeParamRef)(using Context): TypeBounds = nonParamBounds(param).derivedTypeBounds(fullLowerBound(param), fullUpperBound(param)) - /** If true: eliminate wildcards in bounds by avoidance. - * Otherwise replace them by fresh variables, except that - * in mode TypeVarsMissContext, wildcards are always eliminated by approximation. + /** If true, eliminate wildcards in bounds by avoidance, otherwise replace + * them by fresh variables. */ protected def approximateWildcards: Boolean = true @@ -96,7 +95,7 @@ trait ConstraintHandling { if !isUpper then variance = -1 def apply(t: Type): Type = t match case t: WildcardType => - if approximateWildcards || ctx.mode.is(Mode.TypevarsMissContext) then + if approximateWildcards then val bounds = t.effectiveBounds range(bounds.lo, bounds.hi) else diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 1482e472c71c..b8cfc8743609 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -139,7 +139,14 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling try topLevelSubType(tp1, tp2) finally useNecessaryEither = saved - override protected def approximateWildcards: Boolean = useNecessaryEither + /** Use avoidance to get rid of wildcards in constraint bounds if + * we are doing a neccessary comparison, or the mode is TypeVarsMissContext. + * The idea is that under either of these conditions we are not interested + * in creating a fresh type variable to replace the wildcard. I verified + * that several tests break if one or the other part of the disjunction is dropped. + */ + override protected def approximateWildcards: Boolean = + useNecessaryEither || ctx.mode.is(Mode.TypevarsMissContext) def testSubType(tp1: Type, tp2: Type): CompareResult = GADTused = false From ee939f10b20a9db05e1cf47fceabc0a5a61d6910 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 4 Jun 2021 21:34:53 +0200 Subject: [PATCH 10/14] Test assertion --- compiler/src/dotty/tools/dotc/config/Config.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/config/Config.scala b/compiler/src/dotty/tools/dotc/config/Config.scala index da2755b76423..bedef4d0bea3 100644 --- a/compiler/src/dotty/tools/dotc/config/Config.scala +++ b/compiler/src/dotty/tools/dotc/config/Config.scala @@ -35,7 +35,7 @@ object Config { inline val checkConstraintsPropagated = false /** Check that constraint bounds do not contain wildcard types */ - inline val checkNoWildcardsInConstraint = false + inline val checkNoWildcardsInConstraint = true /** If a constraint is over a type lambda `tl` and `tvar` is one of * the type variables associated with `tl` in the constraint, check @@ -49,7 +49,7 @@ object Config { * compiling all of dotty together (source seems to be in GenBCode which * accesses javac's settings.) * - * It is recommended to turn this option on only when chasing down + * It is recommended to turn this option on only w zhen chasing down * a TypeParamRef instantiation error. See comment in Types.TypeVar.instantiate. */ inline val debugCheckConstraintsClosed = false From f0fe97f22bbdfbae0652e234022b048a4e8cdb8a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 5 Jun 2021 12:14:33 +0200 Subject: [PATCH 11/14] Don't let wildcards enter constraints when adding type lambdas --- .../dotty/tools/dotc/core/ConstraintHandling.scala | 14 ++++---------- .../dotty/tools/dotc/core/OrderingConstraint.scala | 4 +++- compiler/src/dotty/tools/dotc/core/Types.scala | 9 +++++++++ 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala index f10796975941..7907201c718e 100644 --- a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -91,17 +91,11 @@ trait ConstraintHandling { // so we shouldn't allow them as constraints either. false else - val dropWildcards = new ApproximatingTypeMap: + val dropWildcards = new AvoidWildcardsMap: if !isUpper then variance = -1 - def apply(t: Type): Type = t match - case t: WildcardType => - if approximateWildcards then - val bounds = t.effectiveBounds - range(bounds.lo, bounds.hi) - else - newTypeVar(apply(t.effectiveBounds).toBounds) - case _ => - mapOver(t) + override def mapWild(t: WildcardType) = + if approximateWildcards then super.mapWild(t) + else newTypeVar(apply(t.effectiveBounds).toBounds) val bound = dropWildcards(rawBound) val oldBounds @ TypeBounds(lo, hi) = constraint.nonParamBounds(param) val equalBounds = (if isUpper then lo else hi) eq bound diff --git a/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala b/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala index 7e6045c8610c..89bf84d1ed03 100644 --- a/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala +++ b/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala @@ -280,9 +280,11 @@ class OrderingConstraint(private val boundsMap: ParamBounds, var current = this val todos = new mutable.ListBuffer[(OrderingConstraint, TypeParamRef) => OrderingConstraint] var i = 0 + val dropWildcards = AvoidWildcardsMap() while (i < poly.paramNames.length) { val param = poly.paramRefs(i) - val stripped = stripParams(nonParamBounds(param), todos, isUpper = true) + val bounds = dropWildcards(nonParamBounds(param)) + val stripped = stripParams(bounds, todos, isUpper = true) current = updateEntry(current, param, stripped) while todos.nonEmpty do current = todos.head(current, param) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 71ef03c0f24d..e0c1c35e850a 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -5701,6 +5701,15 @@ object Types { lo.toText(printer) ~ ".." ~ hi.toText(printer) } + /** Approximate wildcards by their bounds */ + class AvoidWildcardsMap(using Context) extends ApproximatingTypeMap: + protected def mapWild(t: WildcardType) = + val bounds = t.effectiveBounds + range(atVariance(-variance)(apply(bounds.lo)), apply(bounds.hi)) + def apply(t: Type): Type = t match + case t: WildcardType => mapWild(t) + case _ => mapOver(t) + // ----- TypeAccumulators ---------------------------------------------------- abstract class TypeAccumulator[T](implicit protected val accCtx: Context) From 5ed5ef8810fa3262de935d1707c5dd41e33c0080 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 5 Jun 2021 12:24:58 +0200 Subject: [PATCH 12/14] Fix typo --- compiler/src/dotty/tools/dotc/config/Config.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/config/Config.scala b/compiler/src/dotty/tools/dotc/config/Config.scala index bedef4d0bea3..e61b54fb1146 100644 --- a/compiler/src/dotty/tools/dotc/config/Config.scala +++ b/compiler/src/dotty/tools/dotc/config/Config.scala @@ -49,7 +49,7 @@ object Config { * compiling all of dotty together (source seems to be in GenBCode which * accesses javac's settings.) * - * It is recommended to turn this option on only w zhen chasing down + * It is recommended to turn this option on only when chasing down * a TypeParamRef instantiation error. See comment in Types.TypeVar.instantiate. */ inline val debugCheckConstraintsClosed = false From 9e2d250aff15860d481352e721e12b871df65616 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 5 Jun 2021 12:25:20 +0200 Subject: [PATCH 13/14] Turn off "no wildcards in constraint" check --- compiler/src/dotty/tools/dotc/config/Config.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/config/Config.scala b/compiler/src/dotty/tools/dotc/config/Config.scala index e61b54fb1146..da2755b76423 100644 --- a/compiler/src/dotty/tools/dotc/config/Config.scala +++ b/compiler/src/dotty/tools/dotc/config/Config.scala @@ -35,7 +35,7 @@ object Config { inline val checkConstraintsPropagated = false /** Check that constraint bounds do not contain wildcard types */ - inline val checkNoWildcardsInConstraint = true + inline val checkNoWildcardsInConstraint = false /** If a constraint is over a type lambda `tl` and `tvar` is one of * the type variables associated with `tl` in the constraint, check From 25e311fec1341cace733c1f0f2e016e5cb3514f2 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 8 Jun 2021 21:09:39 +0200 Subject: [PATCH 14/14] Address review comments --- compiler/src/dotty/tools/dotc/core/TypeComparer.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index b8cfc8743609..f183e0467571 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -140,10 +140,11 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling finally useNecessaryEither = saved /** Use avoidance to get rid of wildcards in constraint bounds if - * we are doing a neccessary comparison, or the mode is TypeVarsMissContext. + * we are doing a necessary comparison, or the mode is TypeVarsMissContext. * The idea is that under either of these conditions we are not interested * in creating a fresh type variable to replace the wildcard. I verified * that several tests break if one or the other part of the disjunction is dropped. + * (for instance, i12677.scala demands `useNecessaryEither` in the condition) */ override protected def approximateWildcards: Boolean = useNecessaryEither || ctx.mode.is(Mode.TypevarsMissContext)