Skip to content

Commit ea65338

Browse files
authored
Merge pull request #10924 from dotty-staging/fix-#10161
More careful instantation of wildcards when solving constraints
2 parents 1405778 + e9608f0 commit ea65338

File tree

2 files changed

+79
-30
lines changed

2 files changed

+79
-30
lines changed

compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala

Lines changed: 70 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import Flags._
1010
import config.Config
1111
import config.Printers.typr
1212
import reporting.trace
13+
import StdNames.tpnme
1314

1415
/** Methods for adding constraints and solving them.
1516
*
@@ -243,48 +244,87 @@ trait ConstraintHandling {
243244
* @return the instantiating type
244245
* @pre `param` is in the constraint's domain.
245246
*/
246-
final def approximation(param: TypeParamRef, fromBelow: Boolean)(using Context): Type = {
247-
val replaceWildcards = new TypeMap {
247+
final def approximation(param: TypeParamRef, fromBelow: Boolean)(using Context): Type =
248+
249+
/** Substitute wildcards with fresh TypeParamRefs, to be compared with
250+
* other bound, so that they can be instantiated.
251+
*/
252+
object substWildcards extends TypeMap:
253+
override def stopAtStatic = true
254+
255+
var trackedPolis: List[PolyType] = Nil
256+
def apply(tp: Type) = tp match
257+
case tp: WildcardType =>
258+
val poly = PolyType(tpnme.EMPTY :: Nil)(pt => tp.bounds :: Nil, pt => defn.AnyType)
259+
trackedPolis = poly :: trackedPolis
260+
poly.paramRefs.head
261+
case _ =>
262+
mapOver(tp)
263+
end substWildcards
264+
265+
/** Replace TypeParamRefs substituted for wildcards by `substWildCards`
266+
* and any remaining wildcards by a safe approximation
267+
*/
268+
val replaceWildcards = new TypeMap:
248269
override def stopAtStatic = true
270+
271+
/** Try to instantiate a wildcard or TypeParamRef representing a wildcard
272+
* to a type that is known to conform to it.
273+
* This means:
274+
* If fromBelow is true, we minimize the type overall
275+
* Hence, if variance < 0, pick the maximal safe type: bounds.lo
276+
* (i.e. the whole bounds range is over the type).
277+
* If variance > 0, pick the minimal safe type: bounds.hi
278+
* (i.e. the whole bounds range is under the type).
279+
* If variance == 0, pick bounds.lo anyway (this is arbitrary but in line with
280+
* the principle that we pick the smaller type when in doubt).
281+
* If fromBelow is false, we maximize the type overall and reverse the bounds
282+
* If variance != 0. For variance == 0, we still minimize.
283+
* In summary we pick the bound given by this table:
284+
*
285+
* variance | -1 0 1
286+
* ------------------------
287+
* from below | lo lo hi
288+
* from above | hi lo lo
289+
*/
290+
def pickOneBound(bounds: TypeBounds) =
291+
if variance == 0 || fromBelow == (variance < 0) then bounds.lo
292+
else bounds.hi
293+
249294
def apply(tp: Type) = mapOver {
250-
tp match {
295+
tp match
251296
case tp: WildcardType =>
252-
val bounds = tp.optBounds.orElse(TypeBounds.empty).bounds
253-
// Try to instantiate the wildcard to a type that is known to conform to it.
254-
// This means:
255-
// If fromBelow is true, we minimize the type overall
256-
// Hence, if variance < 0, pick the maximal safe type: bounds.lo
257-
// (i.e. the whole bounds range is over the type)
258-
// if variance > 0, pick the minimal safe type: bounds.hi
259-
// (i.e. the whole bounds range is under the type)
260-
// if variance == 0, pick bounds.lo anyway (this is arbitrary but in line with
261-
// the principle that we pick the smaller type when in doubt).
262-
// If fromBelow is false, we maximize the type overall and reverse the bounds
263-
// if variance != 0. For variance == 0, we still minimize.
264-
// In summary we pick the bound given by this table:
265-
//
266-
// variance | -1 0 1
267-
// ------------------------
268-
// from below | lo lo hi
269-
// from above | hi lo lo
270-
//
271-
if (variance == 0 || fromBelow == (variance < 0)) bounds.lo else bounds.hi
297+
pickOneBound(tp.bounds)
298+
case tp: TypeParamRef if substWildcards.trackedPolis.contains(tp.binder) =>
299+
pickOneBound(fullBounds(tp))
272300
case _ => tp
273-
}
274301
}
275-
}
276-
constraint.entry(param) match {
302+
end replaceWildcards
303+
304+
constraint.entry(param) match
277305
case entry: TypeBounds =>
278306
val useLowerBound = fromBelow || param.occursIn(entry.hi)
279-
val bound = if (useLowerBound) fullLowerBound(param) else fullUpperBound(param)
280-
val inst = replaceWildcards(bound)
307+
val rawBound = if useLowerBound then fullLowerBound(param) else fullUpperBound(param)
308+
val bound = substWildcards(rawBound)
309+
val inst =
310+
if bound eq rawBound then bound
311+
else
312+
// Get rid of wildcards by mapping them to fresh TypeParamRefs
313+
// with constraints derived from comparing both bounds, and then
314+
// instantiating. See pos/i10161.scala for a test where this matters.
315+
val saved = constraint
316+
try
317+
for poly <- substWildcards.trackedPolis do addToConstraint(poly, Nil)
318+
if useLowerBound then bound <:< fullUpperBound(param)
319+
else fullLowerBound(param) <:< bound
320+
replaceWildcards(bound)
321+
finally constraint = saved
281322
typr.println(s"approx ${param.show}, from below = $fromBelow, bound = ${bound.show}, inst = ${inst.show}")
282323
inst
283324
case inst =>
284325
assert(inst.exists, i"param = $param\nconstraint = $constraint")
285326
inst
286-
}
287-
}
327+
end approximation
288328

289329
/** If `tp` is an intersection such that some operands are transparent trait instances
290330
* and others are not, replace as many transparent trait instances as possible with Any

tests/pos/i10161.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
object Incompat2 {
2+
trait Context { type Out }
3+
4+
object Context {
5+
def foo(implicit ctx: Context): Option[ctx.Out] = ???
6+
7+
def bar(implicit ctx: Context): (Option[ctx.Out], String) = (foo, "foo")
8+
}
9+
}

0 commit comments

Comments
 (0)