Skip to content

Commit a023600

Browse files
committed
More careful instantiation of type variables
Ensure that: - A type variable is only instantiated once. - A type variable instantiation is allowed given the current constraints. This required two changes: - In dropTransparentTrait, reset the constraints if we decide to keep the transparent traits. - Fix Type#simplify to preserve `=:=` (as its documentation indicate) when dealing with skolems.
1 parent 75556ad commit a023600

File tree

6 files changed

+63
-24
lines changed

6 files changed

+63
-24
lines changed

compiler/src/dotty/tools/dotc/config/Config.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ object Config {
2424
inline val checkConstraintsNonCyclic = false
2525

2626
/** Check that each constraint resulting from a subtype test
27-
* is satisfiable.
27+
* is satisfiable. Also check that a type variable instantiation
28+
* satisfies its constraints.
29+
* Note that this can fail when bad bounds are in scope, like in
30+
* tests/neg/i4721a.scala.
2831
*/
2932
inline val checkConstraintsSatisfiable = false
3033

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,8 +300,15 @@ trait ConstraintHandling {
300300
dropped = dropped.tail
301301
recur(tp)
302302

303+
val saved = ctx.typerState.snapshot()
303304
val tpw = recur(tp)
304-
if (tpw eq tp) || dropped.forall(_ frozen_<:< tpw) then tp else tpw
305+
if (tpw eq tp) || dropped.forall(_ frozen_<:< tpw) then
306+
// Rollback any constraint change that would lead to `tp` no longer
307+
// being a valid solution.
308+
ctx.typerState.resetTo(saved)
309+
tp
310+
else
311+
tpw
305312
end dropTransparentTraits
306313

307314
/** If `tp` is an applied match type alias which is also an unreducible application

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,10 @@ object TypeOps:
170170
if (normed.exists) normed else mapOver
171171
case tp: MethodicType =>
172172
tp // See documentation of `Types#simplified`
173+
case tp: SkolemType =>
174+
// Mapping over a skolem creates a new skolem which by definition won't
175+
// be =:= to the original one.
176+
tp
173177
case _ =>
174178
mapOver
175179
}

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

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4479,8 +4479,14 @@ object Types {
44794479

44804480
/** Instantiate variable with given type */
44814481
def instantiateWith(tp: Type)(using Context): Type = {
4482-
assert(tp ne this, s"self instantiation of ${tp.show}, constraint = ${ctx.typerState.constraint.show}")
4483-
typr.println(s"instantiating ${this.show} with ${tp.show}")
4482+
assert(tp ne this, i"self instantiation of $origin, constraint = ${ctx.typerState.constraint}")
4483+
assert(!myInst.exists, i"$origin is already instantiated to $myInst but we attempted to instantiate it to $tp")
4484+
typr.println(i"instantiating $this with $tp")
4485+
4486+
if Config.checkConstraintsSatisfiable then
4487+
assert(currentEntry.bounds.contains(tp),
4488+
i"$origin is constrained to be $currentEntry but attempted to instantiate it to $tp")
4489+
44844490
if ((ctx.typerState eq owningState.get) && !TypeComparer.subtypeCheckInProgress)
44854491
setInst(tp)
44864492
ctx.typerState.constraint = ctx.typerState.constraint.replace(origin, tp)
@@ -4495,7 +4501,11 @@ object Types {
44954501
* is also a singleton type.
44964502
*/
44974503
def instantiate(fromBelow: Boolean)(using Context): Type =
4498-
instantiateWith(avoidCaptures(TypeComparer.instanceType(origin, fromBelow)))
4504+
val tp = avoidCaptures(TypeComparer.instanceType(origin, fromBelow))
4505+
if myInst.exists then // The line above might have triggered instantiation of the current type variable
4506+
myInst
4507+
else
4508+
instantiateWith(tp)
44994509

45004510
/** For uninstantiated type variables: the entry in the constraint (either bounds or
45014511
* provisional instance value)

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

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -403,20 +403,21 @@ object Inferencing {
403403
val vs = variances(tp)
404404
val patternBindings = new mutable.ListBuffer[(Symbol, TypeParamRef)]
405405
vs foreachBinding { (tvar, v) =>
406-
if (v == 1) tvar.instantiate(fromBelow = false)
407-
else if (v == -1) tvar.instantiate(fromBelow = true)
408-
else {
409-
val bounds = TypeComparer.fullBounds(tvar.origin)
410-
if (bounds.hi <:< bounds.lo || bounds.hi.classSymbol.is(Final) || fromScala2x)
411-
tvar.instantiate(fromBelow = false)
406+
if !tvar.isInstantiated then
407+
if (v == 1) tvar.instantiate(fromBelow = false)
408+
else if (v == -1) tvar.instantiate(fromBelow = true)
412409
else {
413-
// We do not add the created symbols to GADT constraint immediately, since they may have inter-dependencies.
414-
// Instead, we simultaneously add them later on.
415-
val wildCard = newPatternBoundSymbol(UniqueName.fresh(tvar.origin.paramName), bounds, span, addToGadt = false)
416-
tvar.instantiateWith(wildCard.typeRef)
417-
patternBindings += ((wildCard, tvar.origin))
410+
val bounds = TypeComparer.fullBounds(tvar.origin)
411+
if (bounds.hi <:< bounds.lo || bounds.hi.classSymbol.is(Final) || fromScala2x)
412+
tvar.instantiate(fromBelow = false)
413+
else {
414+
// We do not add the created symbols to GADT constraint immediately, since they may have inter-dependencies.
415+
// Instead, we simultaneously add them later on.
416+
val wildCard = newPatternBoundSymbol(UniqueName.fresh(tvar.origin.paramName), bounds, span, addToGadt = false)
417+
tvar.instantiateWith(wildCard.typeRef)
418+
patternBindings += ((wildCard, tvar.origin))
419+
}
418420
}
419-
}
420421
}
421422
val res = patternBindings.toList.map { (boundSym, _) =>
422423
// substitute bounds of pattern bound variables to deal with possible F-bounds
@@ -654,13 +655,16 @@ trait Inferencing { this: Typer =>
654655
while buf.nonEmpty do
655656
val first @ (tvar, fromBelow) = buf.head
656657
buf.dropInPlace(1)
657-
val suspend = buf.exists{ (following, _) =>
658-
if fromBelow then
659-
constraint.isLess(following.origin, tvar.origin)
660-
else
661-
constraint.isLess(tvar.origin, following.origin)
662-
}
663-
if suspend then suspended += first else tvar.instantiate(fromBelow)
658+
if !tvar.isInstantiated then
659+
val suspend = buf.exists{ (following, _) =>
660+
if fromBelow then
661+
constraint.isLess(following.origin, tvar.origin)
662+
else
663+
constraint.isLess(tvar.origin, following.origin)
664+
}
665+
if suspend then suspended += first else tvar.instantiate(fromBelow)
666+
end if
667+
end while
664668
doInstantiate(suspended)
665669
end doInstantiate
666670
doInstantiate(toInstantiate)

tests/neg/transparent-trait.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
transparent trait A
2+
transparent trait B
3+
trait C
4+
5+
object Test:
6+
val x = identity(new A with B) // infers A with B (because there's no non-transparent trait in the intersection)
7+
val x2: A with B = x // OK
8+
9+
val y = identity(new A with B with C) // infers C
10+
val y2: C = y // OK
11+
val y3: A with B = y // error

0 commit comments

Comments
 (0)