Skip to content

Commit bae9509

Browse files
committed
Add comments
1 parent ba7c362 commit bae9509

File tree

8 files changed

+87
-11
lines changed

8 files changed

+87
-11
lines changed

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

+30-3
Original file line numberDiff line numberDiff line change
@@ -463,14 +463,30 @@ trait ConstraintHandling {
463463
}
464464
}
465465

466+
/** Fix instance type `tp` by avoidance so that it does not contain references
467+
* to types at level > `maxLevel`.
468+
* @param tp the type to be fixed
469+
* @param fromBelow whether type was obtained from lower bound
470+
* @param maxLevel the maximum level of references allowed
471+
* @param param the parameter that was instantiated
472+
*/
466473
private def fixLevels(tp: Type, fromBelow: Boolean, maxLevel: Int, param: TypeParamRef)(using Context) =
467474

468475
def needsFix(tp: NamedType) =
469476
(tp.prefix eq NoPrefix) && tp.symbol.nestingLevel > maxLevel
470477

478+
/** An accumulator that determines whether levels need to be fixed
479+
* and computes on the side sets of nested type variables that need
480+
* to be instantiated.
481+
*/
471482
class NeedsLeveling extends TypeAccumulator[Boolean]:
472483
if !fromBelow then variance = -1
484+
485+
/** Nested type variables that should be instiated to theor lower (respoctively
486+
* upper) bounds.
487+
*/
473488
var nestedVarsLo, nestedVarsHi: SimpleIdentitySet[TypeVar] = SimpleIdentitySet.empty
489+
474490
def apply(need: Boolean, tp: Type) =
475491
need || tp.match
476492
case tp: NamedType =>
@@ -483,17 +499,24 @@ trait ConstraintHandling {
483499
if variance > 0 then nestedVarsLo += tp
484500
else if variance < 0 then nestedVarsHi += tp
485501
else tp.nestingLevel = maxLevel
502+
// For invariant type variables, we use a different strategy.
503+
// Rather than instantiating to a bound and then propagating in an
504+
// AvoidMap, change the nesting level of an invariant type
505+
// variable to `maxLevel`. This means that the type variable will be
506+
// instantiated later to a less nested type. If there are other references
507+
// to the same type variable that do not come from the type undergoing
508+
// `fixLevels`, this could lead to coarser types. But it has the potential
509+
// to give a better approximation for the current type, since it avoids forming
510+
// a Range in invariant position, which can lead to very coarse types further out.
486511
true
487512
else false
488513
case _ =>
489514
foldOver(need, tp)
515+
end NeedsLeveling
490516

491517
class LevelAvoidMap extends TypeOps.AvoidMap:
492518
if !fromBelow then variance = -1
493519
def toAvoid(tp: NamedType) = needsFix(tp)
494-
//override def apply(tp: Type): Type = tp match
495-
// case tp: LazyRef => tp
496-
// case _ => super.apply(tp)
497520

498521
if ctx.isAfterTyper then tp
499522
else
@@ -512,6 +535,8 @@ trait ConstraintHandling {
512535
* contains a reference to the parameter itself (such occurrences can arise
513536
* for F-bounded types, `addOneBound` ensures that they never occur in the
514537
* lower bound).
538+
* The solved type is not allowed to contain references to types nested deeper
539+
* than `maxLevel`.
515540
* Wildcard types in bounds are approximated by their upper or lower bounds.
516541
* The constraint is left unchanged.
517542
* @return the instantiating type
@@ -639,6 +664,8 @@ trait ConstraintHandling {
639664
* lower bounds; otherwise it is the glb of its upper bounds. However,
640665
* a lower bound instantiation can be a singleton type only if the upper bound
641666
* is also a singleton type.
667+
* The instance type is not allowed to contain references to types nested deeper
668+
* than `maxLevel`.
642669
*/
643670
def instanceType(param: TypeParamRef, fromBelow: Boolean, maxLevel: Int)(using Context): Type = {
644671
val approx = approximation(param, fromBelow, maxLevel).simplified

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

+3-7
Original file line numberDiff line numberDiff line change
@@ -4501,13 +4501,9 @@ object Types {
45014501
* @param creatorState The typer state in which the variable was created.
45024502
* @param nestingLevel Symbols with a nestingLevel strictly greater than this
45034503
* will not appear in the instantiation of this type variable.
4504-
* This is enforced in `ConstraintHandling` by:
4505-
* - Maintaining the invariant that the `nonParamBounds`
4506-
* of a type variable never refer to a type with a
4507-
* greater `nestingLevel` (see `legalBound` for the reason
4508-
* why this cannot be delayed until instantiation).
4509-
* - On instantiation, replacing any param in the param bound
4510-
* with a level greater than nestingLevel (see `fullLowerBound`).
4504+
* This is enforced in `ConstraintHandling#fixLevels`.
4505+
* The `nestingLevel` of a type variable can be made smaller when
4506+
* fixing the levels for some other type variable instance.
45114507
*/
45124508
final class TypeVar private(initOrigin: TypeParamRef, creatorState: TyperState | Null, var nestingLevel: Int) extends CachedProxyType with ValueType {
45134509
private var currentOrigin = initOrigin

compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala

+2-1
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,8 @@ class InlineReducer(inliner: Inliner)(using Context):
302302

303303
def addTypeBindings(typeBinds: TypeBindsMap)(using Context): Unit =
304304
typeBinds.foreachBinding { case (sym, shouldBeMinimized) =>
305-
newTypeBinding(sym, ctx.gadt.approximation(sym, fromBelow = shouldBeMinimized, Int.MaxValue))
305+
newTypeBinding(sym,
306+
ctx.gadt.approximation(sym, fromBelow = shouldBeMinimized, maxLevel = Int.MaxValue))
306307
}
307308

308309
def registerAsGadtSyms(typeBinds: TypeBindsMap)(using Context): Unit =

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

+4
Original file line numberDiff line numberDiff line change
@@ -1683,6 +1683,10 @@ class Namer { typer: Typer =>
16831683
// are better ways to achieve this. It would be good if we could get rid of this code.
16841684
// It seems at least partially redundant with the nesting level checking on TypeVar
16851685
// instantiation.
1686+
// It turns out if we fix levels on instantiation we still need this code.
1687+
// Examples that fail otherwise are pos/scalaz-redux.scala and pos/java-futures.scala.
1688+
// So fixing levels at instantiation avoids the soundness problem but apparently leads
1689+
// to type inference problems since it comes too late.
16861690
if !Config.checkLevels then
16871691
val hygienicType = TypeOps.avoid(rhsType, termParamss.flatten)
16881692
if (!hygienicType.isValueType || !(hygienicType <:< tpt.tpe))

tests/neg/i8861.scala

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// Compiles under 14026
12
object Test {
23
sealed trait Container { s =>
34
type A

tests/neg/i8900-promote.scala

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// Compiles under #14026
12
class Inv[A <: Singleton](x: A)
23
object Inv {
34
def empty[A <: Singleton]: Inv[A] = new Inv(???)

tests/pos/java-futures.scala

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import java.util.concurrent.{TimeUnit, TimeoutException, Future, Executors => JExecutors}
2+
3+
class TestSource
4+
trait LoggedRunnable extends Runnable
5+
6+
7+
8+
object Test:
9+
10+
val filteredSources: List[TestSource] = ???
11+
12+
def encapsulatedCompilation(testSource: TestSource): LoggedRunnable = ???
13+
14+
def testSuite(): this.type =
15+
val pool = JExecutors.newWorkStealingPool(Runtime.getRuntime.availableProcessors())
16+
val eventualResults = for target <- filteredSources yield
17+
pool.submit(encapsulatedCompilation(target))
18+
19+
for fut <- eventualResults do
20+
try fut.get()
21+
catch case ex: Exception =>
22+
System.err.println(ex.getMessage)
23+
ex.printStackTrace()
24+
25+
this
26+

tests/pos/scalaz-redux.scala

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
2+
sealed abstract class LazyEither[A, B] {
3+
4+
def fold[X](left: (=> A) => X, right: (=> B) => X): X =
5+
this match {
6+
case LazyLeft(a) => left(a())
7+
case LazyRight(b) => right(b())
8+
}
9+
}
10+
11+
object LazyEither {
12+
13+
final case class LeftProjection[A, B](e: LazyEither[A, B]) extends AnyVal {
14+
15+
def getOrElse[AA >: A](default: => AA): AA =
16+
e.fold(z => z, _ => default)
17+
}
18+
}
19+
private case class LazyLeft[A, B](a: () => A) extends LazyEither[A, B]
20+
private case class LazyRight[A, B](b: () => B) extends LazyEither[A, B]

0 commit comments

Comments
 (0)