Skip to content

Commit c87c0f9

Browse files
committed
Trial: check reverse dependencies
1 parent 9bb68b7 commit c87c0f9

File tree

3 files changed

+170
-61
lines changed

3 files changed

+170
-61
lines changed

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,12 @@ abstract class Constraint extends Showable {
166166
*/
167167
def hasConflictingTypeVarsFor(tl: TypeLambda, that: Constraint): Boolean
168168

169+
/** Does `param` occur at the toplevel in `tp` ?
170+
* Toplevel means: the type itself or a factor in some
171+
* combination of `&` or `|` types.
172+
*/
173+
def occursAtToplevel(param: TypeParamRef, tp: Type)(using Context): Boolean
174+
169175
/** A map that associates type parameters of this constraint with all other type
170176
* parameters that refer to them in their bounds covariantly, such that, if the
171177
* type parameter is instantiated to a larger type, the constraint would be narrowed.
@@ -191,12 +197,6 @@ abstract class Constraint extends Showable {
191197
/** Check that no constrained parameter contains itself as a bound */
192198
def checkWellFormed()(using Context): this.type
193199

194-
/** Does `param` occur at the toplevel in `tp` ?
195-
* Toplevel means: the type itself or a factor in some
196-
* combination of `&` or `|` types.
197-
*/
198-
def occursAtToplevel(param: TypeParamRef, tp: Type)(using Context): Boolean
199-
200200
/** Check that constraint only refers to TypeParamRefs bound by itself */
201201
def checkClosed()(using Context): Unit
202202

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

Lines changed: 153 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ import cc.{CapturingType, derivedCapturingType}
1818

1919
object OrderingConstraint {
2020

21+
@sharable private var id = 0
22+
private def nextId =
23+
id += 1
24+
id
25+
2126
type ArrayValuedMap[T] = SimpleIdentityMap[TypeLambda, Array[T]]
2227

2328
/** The type of `OrderingConstraint#boundsMap` */
@@ -140,6 +145,10 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
140145

141146
type This = OrderingConstraint
142147

148+
var id = nextId
149+
//if id == 118 then
150+
// new Error(s"at $id").printStackTrace()
151+
143152
/** A new constraint with given maps and given set of hard typevars */
144153
def newConstraint( // !!! Dotty problem: Making newConstraint `private` causes -Ytest-pickler failure.
145154
boundsMap: ParamBounds = this.boundsMap,
@@ -245,8 +254,40 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
245254
//.showing(i"outer depends on $tv with ${tvdeps.toList}%, % = $result")
246255
if co then test(coDeps, upperLens) else test(contraDeps, lowerLens)
247256

257+
/** Modify traversals in two respects:
258+
* - when encountering an application C[Ts], where C is a type variable or parameter
259+
* that has an instantiation in this constraint, assume the type parameters of
260+
* the instantiation instead of the type parameters of C when traversing the
261+
* arguments Ts. That can make a difference for the variance in which an argument
262+
* is traversed. Example constraint:
263+
*
264+
* constrainded types: C[X], A
265+
* A >: C[B]
266+
* C := Option
267+
*
268+
* Here, B is traversed with variance +1 instead of 0. Test case: pos/t3152.scala
269+
*
270+
* - When typing a prefx, don't avoid negative variances. This matters only for the
271+
* corner case where a parameter is instantiated to Nothing (see comment in
272+
* TypeAccumulator#applyToPrefix). When determining instantiation directions
273+
* (which is what dependency variances are for), it can be ignored.
274+
*/
275+
private trait ConstraintAwareTraversal[T] extends TypeAccumulator[T]:
276+
override def tyconTypeParams(tp: AppliedType)(using Context): List[ParamInfo] =
277+
def tparams(tycon: Type): List[ParamInfo] = tycon match
278+
case tycon: TypeVar if !tycon.isInstantiated => tparams(tycon.origin)
279+
case tycon: TypeParamRef =>
280+
entry(tycon) match
281+
case _: TypeBounds => tp.tyconTypeParams
282+
case tycon1 if tycon1.typeParams.nonEmpty => tycon1.typeParams
283+
case _ => tp.tyconTypeParams
284+
case _ => tp.tyconTypeParams
285+
tparams(tp.tycon)
286+
override def applyToPrefix(x: T, tp: NamedType): T =
287+
this(x, tp.prefix)
288+
248289
private class Adjuster(srcParam: TypeParamRef)(using Context)
249-
extends TypeTraverser, ConstraintAwareTraversal:
290+
extends TypeTraverser, ConstraintAwareTraversal[Unit]:
250291

251292
var add: Boolean = compiletime.uninitialized
252293
val seen = util.HashSet[LazyRef]()
@@ -257,9 +298,12 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
257298

258299
def traverse(t: Type) = t match
259300
case param: TypeParamRef =>
260-
if contains(param) then
261-
if variance >= 0 then coDeps = update(coDeps, param)
262-
if variance <= 0 then contraDeps = update(contraDeps, param)
301+
entry(param) match
302+
case _: TypeBounds =>
303+
if variance >= 0 then coDeps = update(coDeps, param)
304+
if variance <= 0 then contraDeps = update(contraDeps, param)
305+
case tp =>
306+
traverse(tp)
263307
case tp: LazyRef =>
264308
if !seen.contains(tp) then
265309
seen += tp
@@ -408,6 +452,7 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
408452
}
409453

410454
def add(poly: TypeLambda, tvars: List[TypeVar])(using Context): This = {
455+
checkWellFormed() // TODO: drop
411456
assert(!contains(poly))
412457
val nparams = poly.paramNames.length
413458
val entries1 = new Array[Type](nparams * 2)
@@ -596,12 +641,12 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
596641
else
597642
assert(replacement.isValueTypeOrLambda)
598643

599-
val droppedTypeVar = typeVarOfParam(param)
644+
val replacedTypeVar = typeVarOfParam(param)
645+
//println(i"replace $param, $replacedTypeVar with $replacement in $this")
600646

601-
//println(i"replace $param, $droppedTypeVar with $replacement in $this")
602-
val dropTypeVar = new TypeMap:
647+
def mapReplacedTypeVarTo(to: Type) = new TypeMap:
603648
override def apply(t: Type): Type =
604-
if t.exists && (t eq droppedTypeVar) then param else mapOver(t)
649+
if (t eq replacedTypeVar) && t.exists then to else mapOver(t)
605650

606651
var current = this
607652

@@ -616,7 +661,29 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
616661
if other != param then
617662
val oldEntry = current.entry(other)
618663
val newEntry = current.ensureNonCyclic(other, oldEntry.substParam(param, replacement))
619-
current = updateEntryNoOrdering(current, other, newEntry, dropTypeVar(oldEntry))
664+
current = boundsLens.update(this, current, other, newEntry)
665+
var oldDepEntry = oldEntry
666+
var newDepEntry = newEntry
667+
replacedTypeVar match
668+
case tvar: TypeVar =>
669+
if tvar.isInstantiated
670+
then
671+
// replace is called from TypeVar's instantiateWith,
672+
// forget about instantiation for old dependencies
673+
oldDepEntry = mapReplacedTypeVarTo(param)(oldDepEntry)
674+
else
675+
// replace is called from unify,
676+
// assume parameter has been replaced for new dependencies
677+
// (the actual replacement is done below)
678+
newDepEntry = mapReplacedTypeVarTo(replacement)(newDepEntry)
679+
case _ =>
680+
if oldDepEntry ne newDepEntry then
681+
if current eq this then
682+
// We can end up here if oldEntry eq newEntry, so posssibly no new constraint
683+
// was created, but oldDepEntry ne newDepEntry. In that case we must make
684+
// sure we have a new constraint before updating dependencies.
685+
current = newConstraint()
686+
current.adjustDeps(newDepEntry, oldDepEntry, other)
620687
}
621688

622689
current =
@@ -703,6 +770,26 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
703770
assert(tvar.origin == param, i"mismatch $tvar, $param")
704771
case _ =>
705772

773+
def occursAtToplevel(param: TypeParamRef, inst: Type)(using Context): Boolean =
774+
def occurs(tp: Type)(using Context): Boolean = tp match
775+
case tp: AndOrType =>
776+
occurs(tp.tp1) || occurs(tp.tp2)
777+
case tp: TypeParamRef =>
778+
(tp eq param) || entry(tp).match
779+
case NoType => false
780+
case TypeBounds(lo, hi) => (lo eq hi) && occurs(lo)
781+
case inst => occurs(inst)
782+
case tp: TypeVar =>
783+
occurs(tp.underlying)
784+
case TypeBounds(lo, hi) =>
785+
occurs(lo) || occurs(hi)
786+
case _ =>
787+
val tp1 = tp.dealias
788+
(tp1 ne tp) && occurs(tp1)
789+
790+
occurs(inst)
791+
end occursAtToplevel
792+
706793
// ---------- Exploration --------------------------------------------------------
707794

708795
def domainLambdas: List[TypeLambda] = boundsMap.keys
@@ -755,7 +842,60 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
755842

756843
// ---------- Checking -----------------------------------------------
757844

845+
/** Depending on Config settngs, check that there are no cycles and that
846+
* reverse depenecies are correct.
847+
*/
758848
def checkWellFormed()(using Context): this.type =
849+
850+
/** Check that each dependency A -> B in coDeps and contraDeps corresponds to
851+
* a reference to A at the right variance in the entry of B.
852+
*/
853+
def checkBackward(deps: ReverseDeps, depsName: String, v: Int)(using Context): Unit =
854+
deps.foreachBinding { (param, params) =>
855+
for srcParam <- params do
856+
assert(contains(srcParam) && occursAtVariance(param, v, in = entry(srcParam)),
857+
i"wrong $depsName backwards reference $param -> $srcParam in $thisConstraint")
858+
}
859+
860+
/** A type traverser that checks that all references bound in the constraint
861+
* are accounted for in coDeps and/or contraDeps.
862+
*/
863+
def checkForward(srcParam: TypeParamRef)(using Context) =
864+
new TypeTraverser with ConstraintAwareTraversal[Unit]:
865+
val seen = util.HashSet[LazyRef]()
866+
def traverse(t: Type): Unit = t match
867+
case param: TypeParamRef if param ne srcParam =>
868+
def check(deps: ReverseDeps, directDeps: List[TypeParamRef], depsName: String) =
869+
assert(deps.at(param).contains(srcParam) || directDeps.contains(srcParam),
870+
i"missing $depsName backwards reference $param -> $srcParam in $thisConstraint")
871+
entry(param) match
872+
case _: TypeBounds =>
873+
if variance >= 0 then check(contraDeps, upper(param), "contra")
874+
if variance <= 0 then check(coDeps, lower(param), "co")
875+
case tp =>
876+
traverse(tp)
877+
case tp: LazyRef =>
878+
if !seen.contains(tp) then
879+
seen += tp
880+
traverse(tp.ref)
881+
case _ => traverseChildren(t)
882+
883+
/** Does `param` occur at variance `v` or else at variance 0 in entry `in`? */
884+
def occursAtVariance(param: TypeParamRef, v: Int, in: Type)(using Context): Boolean =
885+
val test = new TypeAccumulator[Boolean] with ConstraintAwareTraversal[Boolean]:
886+
def apply(x: Boolean, t: Type): Boolean =
887+
if x then true
888+
else t match
889+
case t: TypeParamRef =>
890+
entry(t) match
891+
case _: TypeBounds =>
892+
t == param && (variance == 0 || variance == v)
893+
case e =>
894+
apply(x, e)
895+
case _ =>
896+
foldOver(x, t)
897+
test(false, in)
898+
759899
if Config.checkConstraintsNonCyclic then
760900
domainParams.foreach { param =>
761901
val inst = entry(param)
@@ -765,37 +905,13 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
765905
s"cyclic bound for $param: ${inst.show} in ${this.show}")
766906
}
767907
if Config.checkConstraintDeps then
768-
def checkDeps(deps: ReverseDeps) = ()/*
769-
deps.foreachBinding { (tv, tvs) =>
770-
for tv1 <- tvs do
771-
assert(!tv1.instanceOpt.exists, i"$this")
772-
}*/
773-
checkDeps(coDeps)
774-
checkDeps(contraDeps)
908+
checkBackward(coDeps, "co", -1)
909+
checkBackward(contraDeps, "contra", +1)
910+
domainParams.foreach(p => if contains(p) then checkForward(p).traverse(entry(p)))
911+
775912
this
776913
end checkWellFormed
777914

778-
def occursAtToplevel(param: TypeParamRef, inst: Type)(using Context): Boolean =
779-
780-
def occurs(tp: Type)(using Context): Boolean = tp match
781-
case tp: AndOrType =>
782-
occurs(tp.tp1) || occurs(tp.tp2)
783-
case tp: TypeParamRef =>
784-
(tp eq param) || entry(tp).match
785-
case NoType => false
786-
case TypeBounds(lo, hi) => (lo eq hi) && occurs(lo)
787-
case inst => occurs(inst)
788-
case tp: TypeVar =>
789-
occurs(tp.underlying)
790-
case TypeBounds(lo, hi) =>
791-
occurs(lo) || occurs(hi)
792-
case _ =>
793-
val tp1 = tp.dealias
794-
(tp1 ne tp) && occurs(tp1)
795-
796-
occurs(inst)
797-
end occursAtToplevel
798-
799915
override def checkClosed()(using Context): Unit =
800916

801917
def isFreeTypeParamRef(tp: Type) = tp match

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

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5501,16 +5501,6 @@ object Types {
55015501
tp.tyconTypeParams
55025502
end VariantTraversal
55035503

5504-
trait ConstraintAwareTraversal extends VariantTraversal:
5505-
override def tyconTypeParams(tp: AppliedType)(using Context): List[ParamInfo] =
5506-
tp.tycon match
5507-
case tycon: TypeParamRef =>
5508-
ctx.typerState.constraint.entry(tycon) match
5509-
case _: TypeBounds =>
5510-
case tp1 => if tp1.typeParams.nonEmpty then return tp1.typeParams
5511-
case _ =>
5512-
tp.tyconTypeParams
5513-
55145504
/** A supertrait for some typemaps that are bijections. Used for capture checking.
55155505
* BiTypeMaps should map capture references to capture references.
55165506
*/
@@ -5616,13 +5606,7 @@ object Types {
56165606
case tp: NamedType =>
56175607
if stopBecauseStaticOrLocal(tp) then tp
56185608
else
5619-
val prefix1 = atVariance(variance max 0)(this(tp.prefix))
5620-
// A prefix is never contravariant. Even if say `p.A` is used in a contravariant
5621-
// context, we cannot assume contravariance for `p` because `p`'s lower
5622-
// bound might not have a binding for `A` (e.g. the lower bound could be `Nothing`).
5623-
// By contrast, covariance does translate to the prefix, since we have that
5624-
// if `p <: q` then `p.A <: q.A`, and well-formedness requires that `A` is a member
5625-
// of `p`'s upper bound.
5609+
val prefix1 = atVariance(variance max 0)(this(tp.prefix)) // see comment of TypeAccumulator's applyToPrefix
56265610
derivedSelect(tp, prefix1)
56275611

56285612
case tp: AppliedType =>
@@ -6075,7 +6059,16 @@ object Types {
60756059

60766060
protected def applyToAnnot(x: T, annot: Annotation): T = x // don't go into annotations
60776061

6078-
protected final def applyToPrefix(x: T, tp: NamedType): T =
6062+
/** A prefix is never contravariant. Even if say `p.A` is used in a contravariant
6063+
* context, we cannot assume contravariance for `p` because `p`'s lower
6064+
* bound might not have a binding for `A`, since the lower bound could be `Nothing`.
6065+
* By contrast, covariance does translate to the prefix, since we have that
6066+
* if `p <: q` then `p.A <: q.A`, and well-formedness requires that `A` is a member
6067+
* of `p`'s upper bound.
6068+
* Overridden in traversers that compute or check reverse dependencies in OrderingConstraint,
6069+
* where we use a more relaxed scheme.
6070+
*/
6071+
protected def applyToPrefix(x: T, tp: NamedType): T =
60796072
atVariance(variance max 0)(this(x, tp.prefix)) // see remark on NamedType case in TypeMap
60806073

60816074
def foldOver(x: T, tp: Type): T = {

0 commit comments

Comments
 (0)