Skip to content

Commit 39b33f5

Browse files
committed
Experiment: forced instantiation only uses non-param bound
Type variable are normally either instantiated to their lower bound (after some widening in `widenInferred`) or upper bound. This commit changes this behavior when the type variable instantiation is forced (which happens in particular before doing an implicit search, but also when typing the parameters of a lambda). We now use the "nonParam" bound instead where other type parameters have been stripped. For example in i16328.scala, when typing id(fromParam((x: Base) => x)) we end up with id[?S](getParamType[?T]((x: Int) => x)) where ?T <: ?S ?T <: Base Before this commit, ?T was then instantiated to ?S & Base (via Typer#adaptNoArgsImplicitMethod#instantiate), which lead to an implicit search failure since ?S was left unconstrained. With this commit, we instead instantiate ?T to its non-param bound `Base` and propagate this to `?S` itself: ?T := Base ?S >: Base The idea is that this leads to constraints being propagated in a more intuitive way (?S will now be instantiated to Base rather than Nothing or Any), however this also means that we're adding a whole new way of instantiating type variables for a seemingly limited number of usecases (see also the discussion starting at scala#16328 (comment)). It's not clear if this won't end up breaking as many things as it fixes, and it would increase our maintenance burden, so I recommend holding off on this change for now.
1 parent b355344 commit 39b33f5

File tree

5 files changed

+76
-15
lines changed

5 files changed

+76
-15
lines changed

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

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -542,12 +542,16 @@ trait ConstraintHandling {
542542
* @return the instantiating type
543543
* @pre `param` is in the constraint's domain.
544544
*/
545-
final def approximation(param: TypeParamRef, fromBelow: Boolean, maxLevel: Int)(using Context): Type =
545+
final def approximation(param: TypeParamRef, fromBelow: Boolean, maxLevel: Int, nonParam: Boolean = false)(using Context): Type =
546546
constraint.entry(param) match
547547
case entry: TypeBounds =>
548548
val useLowerBound = fromBelow || param.occursIn(entry.hi)
549549
val rawInst = withUntrustedBounds(
550-
if useLowerBound then fullLowerBound(param) else fullUpperBound(param))
550+
if nonParam then
551+
val bounds = nonParamBounds(param)
552+
if useLowerBound then bounds.lo else bounds.hi
553+
else
554+
if useLowerBound then fullLowerBound(param) else fullUpperBound(param))
551555
val levelInst = fixLevels(rawInst, fromBelow, maxLevel, param)
552556
if levelInst ne rawInst then
553557
typr.println(i"level avoid for $maxLevel: $rawInst --> $levelInst")
@@ -701,8 +705,8 @@ trait ConstraintHandling {
701705
* The instance type is not allowed to contain references to types nested deeper
702706
* than `maxLevel`.
703707
*/
704-
def instanceType(param: TypeParamRef, fromBelow: Boolean, widenUnions: Boolean, maxLevel: Int)(using Context): Type = {
705-
val approx = approximation(param, fromBelow, maxLevel).simplified
708+
def instanceType(param: TypeParamRef, fromBelow: Boolean, widenUnions: Boolean, maxLevel: Int, nonParam: Boolean = false)(using Context): Type = {
709+
val approx = approximation(param, fromBelow, maxLevel, nonParam).simplified
706710
if fromBelow then
707711
val widened = widenInferred(approx, param, widenUnions)
708712
// Widening can add extra constraints, in particular the widened type might
@@ -712,7 +716,7 @@ trait ConstraintHandling {
712716
// (we do not check for non-toplevel occurences: those should never occur
713717
// since `addOneBound` disallows recursive lower bounds).
714718
if constraint.occursAtToplevel(param, widened) then
715-
instanceType(param, fromBelow, widenUnions, maxLevel)
719+
instanceType(param, fromBelow, widenUnions, maxLevel, nonParam)
716720
else
717721
widened
718722
else

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

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2990,15 +2990,20 @@ object TypeComparer {
29902990
def subtypeCheckInProgress(using Context): Boolean =
29912991
comparing(_.subtypeCheckInProgress)
29922992

2993-
def instanceType(param: TypeParamRef, fromBelow: Boolean, widenUnions: Boolean, maxLevel: Int = Int.MaxValue)(using Context): Type =
2994-
comparing(_.instanceType(param, fromBelow, widenUnions, maxLevel))
2993+
def instanceType(param: TypeParamRef, fromBelow: Boolean,
2994+
widenUnions: Boolean, maxLevel: Int = Int.MaxValue, nonParam: Boolean = false)(using Context): Type =
2995+
comparing(_.instanceType(param, fromBelow, widenUnions, maxLevel, nonParam))
29952996

2996-
def approximation(param: TypeParamRef, fromBelow: Boolean, maxLevel: Int = Int.MaxValue)(using Context): Type =
2997-
comparing(_.approximation(param, fromBelow, maxLevel))
2997+
def approximation(param: TypeParamRef, fromBelow: Boolean,
2998+
maxLevel: Int = Int.MaxValue, nonParam: Boolean = false)(using Context): Type =
2999+
comparing(_.approximation(param, fromBelow, maxLevel, nonParam))
29983000

29993001
def bounds(param: TypeParamRef)(using Context): TypeBounds =
30003002
comparing(_.bounds(param))
30013003

3004+
def nonParamBounds(param: TypeParamRef)(using Context): TypeBounds =
3005+
comparing(_.nonParamBounds(param))
3006+
30023007
def fullBounds(param: TypeParamRef)(using Context): TypeBounds =
30033008
comparing(_.fullBounds(param))
30043009

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

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4755,12 +4755,19 @@ object Types {
47554755
* instantiation can be a singleton type only if the upper bound
47564756
* is also a singleton type.
47574757
*/
4758-
def instantiate(fromBelow: Boolean)(using Context): Type =
4759-
val tp = TypeComparer.instanceType(origin, fromBelow, widenUnions, nestingLevel)
4758+
def instantiate(fromBelow: Boolean, nonParam: Boolean = false)(using Context): Type =
4759+
val tp = TypeComparer.instanceType(origin, fromBelow, widenUnions, nestingLevel, nonParam)
47604760
if myInst.exists then // The line above might have triggered instantiation of the current type variable
47614761
myInst
47624762
else
4763-
instantiateWith(tp)
4763+
if nonParam then
4764+
if this =:= tp then
4765+
instantiateWith(tp)
4766+
else
4767+
// fallback if instantiating to the non-param bound failed, should only happen in case of errors?
4768+
instantiate(fromBelow, nonParam = false)
4769+
else
4770+
instantiateWith(tp)
47644771

47654772
/** Widen unions when instantiating this variable in the current context? */
47664773
def widenUnions(using Context): Boolean = !ctx.typerState.constraint.isHard(this)

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

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ object Inferencing {
163163
(using Context) extends TypeAccumulator[Boolean] {
164164

165165
private def instantiate(tvar: TypeVar, fromBelow: Boolean): Type = {
166-
val inst = tvar.instantiate(fromBelow)
166+
val inst = tvar.instantiate(fromBelow, nonParam = true)
167167
typr.println(i"forced instantiation of ${tvar.origin} = $inst")
168168
inst
169169
}
@@ -367,15 +367,40 @@ object Inferencing {
367367
occurring(tree, boundVars(tree, Nil), Nil)
368368
}
369369

370+
//TODO: duplication with OrderingConstraint#stripParams
371+
private def stripParams(tp: Type, isUpper: Boolean)(using Context): Type = tp match
372+
case param: TypeParamRef if ctx.typerState.constraint.contains(param) =>
373+
NoType
374+
case tp: TypeBounds =>
375+
val lo1 = stripParams(tp.lo, !isUpper).orElse(defn.NothingType)
376+
val hi1 = stripParams(tp.hi, isUpper).orElse(tp.topType)
377+
tp.derivedTypeBounds(lo1, hi1)
378+
case tp: AndType if isUpper =>
379+
val tp1 = stripParams(tp.tp1, isUpper)
380+
val tp2 = stripParams(tp.tp2, isUpper)
381+
if (tp1.exists)
382+
if (tp2.exists) tp.derivedAndType(tp1, tp2)
383+
else tp1
384+
else tp2
385+
case tp: OrType if !isUpper =>
386+
val tp1 = stripParams(tp.tp1, isUpper)
387+
val tp2 = stripParams(tp.tp2, isUpper)
388+
if (tp1.exists)
389+
if (tp2.exists) tp.derivedOrType(tp1, tp2)
390+
else tp1
391+
else tp2
392+
case _ =>
393+
tp
394+
370395
/** The instantiation direction for given poly param computed
371396
* from the constraint:
372397
* @return 1 (maximize) if constraint is uniformly from above,
373398
* -1 (minimize) if constraint is uniformly from below,
374399
* 0 if unconstrained, or constraint is from below and above.
375400
*/
376401
private def instDirection(param: TypeParamRef)(using Context): Int = {
377-
val constrained = TypeComparer.fullBounds(param)
378-
val original = param.binder.paramInfos(param.paramNum)
402+
val constrained = TypeComparer.nonParamBounds(param)
403+
val original = stripParams(param.binder.paramInfos(param.paramNum), isUpper = true).asInstanceOf[TypeBounds]
379404
val cmp = TypeComparer
380405
val approxBelow =
381406
if (!cmp.isSubTypeWhenFrozen(constrained.lo, original.lo)) 1 else 0

tests/pos/i16328.scala

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
trait Base
2+
trait Sub extends Base
3+
4+
trait Foo[T]:
5+
val x: T
6+
object Foo:
7+
given Foo[Base] with
8+
val x: Base = new Base {}
9+
given Foo[Sub] with
10+
val x: Sub = new Sub {}
11+
12+
class Test:
13+
def fromParam[T](x: T => Any)(using foo: Foo[T]): T = foo.x
14+
15+
def id[S](x: S): S = x
16+
17+
def test =
18+
fromParam((x: Base) => x) // ok before
19+
id(fromParam((x: Base) => x)) // ok now (before: ambiguous given for Foo[Base & S])
20+
id(id(fromParam((x: Base) => x))) // ok now (before: ambiguous given for Foo[Base & S])

0 commit comments

Comments
 (0)