Skip to content

Commit 3ef991d

Browse files
authored
Merge pull request #5736 from dotty-staging/skolem-based-gadt-constraints
Skolem based gadt constraints
2 parents 0f8c950 + 3c3b3db commit 3ef991d

31 files changed

+865
-372
lines changed

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

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,18 @@ abstract class Constraint extends Showable {
4545
/** The parameters that are known to be greater wrt <: than `param` */
4646
def upper(param: TypeParamRef): List[TypeParamRef]
4747

48+
/** The lower dominator set.
49+
*
50+
* This is like `lower`, except that each parameter returned is no smaller than every other returned parameter.
51+
*/
52+
def minLower(param: TypeParamRef): List[TypeParamRef]
53+
54+
/** The upper dominator set.
55+
*
56+
* This is like `upper`, except that each parameter returned is no greater than every other returned parameter.
57+
*/
58+
def minUpper(param: TypeParamRef): List[TypeParamRef]
59+
4860
/** lower(param) \ lower(butNot) */
4961
def exclusiveLower(param: TypeParamRef, butNot: TypeParamRef): List[TypeParamRef]
5062

@@ -58,15 +70,6 @@ abstract class Constraint extends Showable {
5870
*/
5971
def nonParamBounds(param: TypeParamRef)(implicit ctx: Context): TypeBounds
6072

61-
/** The lower bound of `param` including all known-to-be-smaller parameters */
62-
def fullLowerBound(param: TypeParamRef)(implicit ctx: Context): Type
63-
64-
/** The upper bound of `param` including all known-to-be-greater parameters */
65-
def fullUpperBound(param: TypeParamRef)(implicit ctx: Context): Type
66-
67-
/** The bounds of `param` including all known-to-be-smaller and -greater parameters */
68-
def fullBounds(param: TypeParamRef)(implicit ctx: Context): TypeBounds
69-
7073
/** A new constraint which is derived from this constraint by adding
7174
* entries for all type parameters of `poly`.
7275
* @param tvars A list of type variables associated with the params,

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

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@ package dotty.tools
22
package dotc
33
package core
44

5-
import Types._, Contexts._, Symbols._
5+
import Types._
6+
import Contexts._
7+
import Symbols._
68
import Decorators._
79
import config.Config
810
import config.Printers.{constr, typr}
11+
import dotty.tools.dotc.reporting.trace
912

1013
/** Methods for adding constraints and solving them.
1114
*
@@ -66,6 +69,22 @@ trait ConstraintHandling[AbstractContext] {
6669
case tp => tp
6770
}
6871

72+
def nonParamBounds(param: TypeParamRef)(implicit actx: AbstractContext): TypeBounds = constraint.nonParamBounds(param)
73+
74+
def fullLowerBound(param: TypeParamRef)(implicit actx: AbstractContext): Type =
75+
(nonParamBounds(param).lo /: constraint.minLower(param))(_ | _)
76+
77+
def fullUpperBound(param: TypeParamRef)(implicit actx: AbstractContext): Type =
78+
(nonParamBounds(param).hi /: constraint.minUpper(param))(_ & _)
79+
80+
/** Full bounds of `param`, including other lower/upper params.
81+
*
82+
* Note that underlying operations perform subtype checks - for this reason, recursing on `fullBounds`
83+
* of some param when comparing types might lead to infinite recursion. Consider `bounds` instead.
84+
*/
85+
def fullBounds(param: TypeParamRef)(implicit actx: AbstractContext): TypeBounds =
86+
nonParamBounds(param).derivedTypeBounds(fullLowerBound(param), fullUpperBound(param))
87+
6988
protected def addOneBound(param: TypeParamRef, bound: Type, isUpper: Boolean)(implicit actx: AbstractContext): Boolean =
7089
!constraint.contains(param) || {
7190
def occursIn(bound: Type): Boolean = {
@@ -262,7 +281,7 @@ trait ConstraintHandling[AbstractContext] {
262281
}
263282
constraint.entry(param) match {
264283
case _: TypeBounds =>
265-
val bound = if (fromBelow) constraint.fullLowerBound(param) else constraint.fullUpperBound(param)
284+
val bound = if (fromBelow) fullLowerBound(param) else fullUpperBound(param)
266285
val inst = avoidParam(bound)
267286
typr_println(s"approx ${param.show}, from below = $fromBelow, bound = ${bound.show}, inst = ${inst.show}")
268287
inst

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

Lines changed: 5 additions & 234 deletions
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,9 @@ object Contexts {
139139
final def importInfo: ImportInfo = _importInfo
140140

141141
/** The current bounds in force for type parameters appearing in a GADT */
142-
private[this] var _gadt: GADTMap = _
143-
protected def gadt_=(gadt: GADTMap): Unit = _gadt = gadt
144-
final def gadt: GADTMap = _gadt
142+
private[this] var _gadt: GadtConstraint = _
143+
protected def gadt_=(gadt: GadtConstraint): Unit = _gadt = gadt
144+
final def gadt: GadtConstraint = _gadt
145145

146146
/** The history of implicit searches that are currently active */
147147
private[this] var _searchHistory: SearchHistory = null
@@ -534,7 +534,7 @@ object Contexts {
534534
def setTypeAssigner(typeAssigner: TypeAssigner): this.type = { this.typeAssigner = typeAssigner; this }
535535
def setTyper(typer: Typer): this.type = { this.scope = typer.scope; setTypeAssigner(typer) }
536536
def setImportInfo(importInfo: ImportInfo): this.type = { this.importInfo = importInfo; this }
537-
def setGadt(gadt: GADTMap): this.type = { this.gadt = gadt; this }
537+
def setGadt(gadt: GadtConstraint): this.type = { this.gadt = gadt; this }
538538
def setFreshGADTBounds: this.type = setGadt(gadt.fresh)
539539
def setSearchHistory(searchHistory: SearchHistory): this.type = { this.searchHistory = searchHistory; this }
540540
def setSource(source: SourceFile): this.type = { this.source = source; this }
@@ -617,7 +617,7 @@ object Contexts {
617617
store = initialStore.updated(settingsStateLoc, settingsGroup.defaultState)
618618
typeComparer = new TypeComparer(this)
619619
searchHistory = new SearchRoot
620-
gadt = EmptyGADTMap
620+
gadt = EmptyGadtConstraint
621621
}
622622

623623
@sharable object NoContext extends Context(null) {
@@ -774,233 +774,4 @@ object Contexts {
774774
if (thread == null) thread = Thread.currentThread()
775775
else assert(thread == Thread.currentThread(), "illegal multithreaded access to ContextBase")
776776
}
777-
778-
sealed abstract class GADTMap {
779-
def addEmptyBounds(sym: Symbol)(implicit ctx: Context): Unit
780-
def addBound(sym: Symbol, bound: Type, isUpper: Boolean)(implicit ctx: Context): Boolean
781-
def bounds(sym: Symbol)(implicit ctx: Context): TypeBounds
782-
def contains(sym: Symbol)(implicit ctx: Context): Boolean
783-
def approximation(sym: Symbol, fromBelow: Boolean)(implicit ctx: Context): Type
784-
def debugBoundsDescription(implicit ctx: Context): String
785-
def fresh: GADTMap
786-
def restore(other: GADTMap): Unit
787-
def isEmpty: Boolean
788-
}
789-
790-
final class SmartGADTMap private (
791-
private var myConstraint: Constraint,
792-
private var mapping: SimpleIdentityMap[Symbol, TypeVar],
793-
private var reverseMapping: SimpleIdentityMap[TypeParamRef, Symbol],
794-
private var boundCache: SimpleIdentityMap[Symbol, TypeBounds]
795-
) extends GADTMap with ConstraintHandling[Context] {
796-
import dotty.tools.dotc.config.Printers.{gadts, gadtsConstr}
797-
798-
def this() = this(
799-
myConstraint = new OrderingConstraint(SimpleIdentityMap.Empty, SimpleIdentityMap.Empty, SimpleIdentityMap.Empty),
800-
mapping = SimpleIdentityMap.Empty,
801-
reverseMapping = SimpleIdentityMap.Empty,
802-
boundCache = SimpleIdentityMap.Empty
803-
)
804-
805-
implicit override def ctx(implicit ctx: Context): Context = ctx
806-
807-
override protected def constraint = myConstraint
808-
override protected def constraint_=(c: Constraint) = myConstraint = c
809-
810-
override def isSubType(tp1: Type, tp2: Type)(implicit ctx: Context): Boolean = ctx.typeComparer.isSubType(tp1, tp2)
811-
override def isSameType(tp1: Type, tp2: Type)(implicit ctx: Context): Boolean = ctx.typeComparer.isSameType(tp1, tp2)
812-
813-
override def addEmptyBounds(sym: Symbol)(implicit ctx: Context): Unit = tvar(sym)
814-
815-
override def addBound(sym: Symbol, bound: Type, isUpper: Boolean)(implicit ctx: Context): Boolean = try {
816-
boundCache = SimpleIdentityMap.Empty
817-
boundAdditionInProgress = true
818-
@annotation.tailrec def stripInternalTypeVar(tp: Type): Type = tp match {
819-
case tv: TypeVar =>
820-
val inst = instType(tv)
821-
if (inst.exists) stripInternalTypeVar(inst) else tv
822-
case _ => tp
823-
}
824-
825-
def externalizedSubtype(tp1: Type, tp2: Type, isSubtype: Boolean): Boolean = {
826-
val externalizedTp1 = removeTypeVars(tp1)
827-
val externalizedTp2 = removeTypeVars(tp2)
828-
829-
(
830-
if (isSubtype) externalizedTp1 frozen_<:< externalizedTp2
831-
else externalizedTp2 frozen_<:< externalizedTp1
832-
).reporting({ res =>
833-
val descr = i"$externalizedTp1 frozen_${if (isSubtype) "<:<" else ">:>"} $externalizedTp2"
834-
i"$descr = $res"
835-
}, gadts)
836-
}
837-
838-
val symTvar: TypeVar = stripInternalTypeVar(tvar(sym)) match {
839-
case tv: TypeVar => tv
840-
case inst =>
841-
val externalizedInst = removeTypeVars(inst)
842-
gadts.println(i"instantiated: $sym -> $externalizedInst")
843-
return if (isUpper) isSubType(externalizedInst , bound) else isSubType(bound, externalizedInst)
844-
}
845-
846-
val internalizedBound = insertTypeVars(bound)
847-
(
848-
stripInternalTypeVar(internalizedBound) match {
849-
case boundTvar: TypeVar =>
850-
if (boundTvar eq symTvar) true
851-
else if (isUpper) addLess(symTvar.origin, boundTvar.origin)
852-
else addLess(boundTvar.origin, symTvar.origin)
853-
case bound =>
854-
if (externalizedSubtype(symTvar, bound, isSubtype = !isUpper)) {
855-
gadts.println(i"manually unifying $symTvar with $bound")
856-
constraint = constraint.updateEntry(symTvar.origin, bound)
857-
true
858-
}
859-
else if (isUpper) addUpperBound(symTvar.origin, bound)
860-
else addLowerBound(symTvar.origin, bound)
861-
}
862-
).reporting({ res =>
863-
val descr = if (isUpper) "upper" else "lower"
864-
val op = if (isUpper) "<:" else ">:"
865-
i"adding $descr bound $sym $op $bound = $res\t( $symTvar $op $internalizedBound )"
866-
}, gadts)
867-
} finally boundAdditionInProgress = false
868-
869-
override def bounds(sym: Symbol)(implicit ctx: Context): TypeBounds = {
870-
mapping(sym) match {
871-
case null => null
872-
case tv =>
873-
def retrieveBounds: TypeBounds = {
874-
val tb = constraint.fullBounds(tv.origin)
875-
removeTypeVars(tb).asInstanceOf[TypeBounds]
876-
}
877-
(
878-
if (boundAdditionInProgress || ctx.mode.is(Mode.GADTflexible)) retrieveBounds
879-
else boundCache(sym) match {
880-
case tb: TypeBounds => tb
881-
case null =>
882-
val bounds = retrieveBounds
883-
boundCache = boundCache.updated(sym, bounds)
884-
bounds
885-
}
886-
).reporting({ res =>
887-
// i"gadt bounds $sym: $res"
888-
""
889-
}, gadts)
890-
}
891-
}
892-
893-
override def contains(sym: Symbol)(implicit ctx: Context): Boolean = mapping(sym) ne null
894-
895-
override def approximation(sym: Symbol, fromBelow: Boolean)(implicit ctx: Context): Type = {
896-
val res = removeTypeVars(approximation(tvar(sym).origin, fromBelow = fromBelow))
897-
gadts.println(i"approximating $sym ~> $res")
898-
res
899-
}
900-
901-
override def fresh: GADTMap = new SmartGADTMap(
902-
myConstraint,
903-
mapping,
904-
reverseMapping,
905-
boundCache
906-
)
907-
908-
def restore(other: GADTMap): Unit = other match {
909-
case other: SmartGADTMap =>
910-
this.myConstraint = other.myConstraint
911-
this.mapping = other.mapping
912-
this.reverseMapping = other.reverseMapping
913-
this.boundCache = other.boundCache
914-
case _ => ;
915-
}
916-
917-
override def isEmpty: Boolean = mapping.size == 0
918-
919-
// ---- Private ----------------------------------------------------------
920-
921-
private[this] def tvar(sym: Symbol)(implicit ctx: Context): TypeVar = {
922-
mapping(sym) match {
923-
case tv: TypeVar =>
924-
tv
925-
case null =>
926-
val res = {
927-
import NameKinds.DepParamName
928-
// avoid registering the TypeVar with TyperState / TyperState#constraint
929-
// - we don't want TyperState instantiating these TypeVars
930-
// - we don't want TypeComparer constraining these TypeVars
931-
val poly = PolyType(DepParamName.fresh(sym.name.toTypeName) :: Nil)(
932-
pt => (sym.info match {
933-
case tb @ TypeBounds(_, hi) if hi.isLambdaSub => tb
934-
case _ => TypeBounds.empty
935-
}) :: Nil,
936-
pt => defn.AnyType)
937-
new TypeVar(poly.paramRefs.head, creatorState = null)
938-
}
939-
gadts.println(i"GADTMap: created tvar $sym -> $res")
940-
constraint = constraint.add(res.origin.binder, res :: Nil)
941-
mapping = mapping.updated(sym, res)
942-
reverseMapping = reverseMapping.updated(res.origin, sym)
943-
res
944-
}
945-
}
946-
947-
private def insertTypeVars(tp: Type, map: TypeMap = null)(implicit ctx: Context) = tp match {
948-
case tp: TypeRef =>
949-
val sym = tp.typeSymbol
950-
if (contains(sym)) tvar(sym) else tp
951-
case _ =>
952-
(if (map != null) map else new TypeVarInsertingMap()).mapOver(tp)
953-
}
954-
private final class TypeVarInsertingMap(implicit ctx: Context) extends TypeMap {
955-
override def apply(tp: Type): Type = insertTypeVars(tp, this)
956-
}
957-
958-
private def removeTypeVars(tp: Type, map: TypeMap = null)(implicit ctx: Context) = tp match {
959-
case tpr: TypeParamRef =>
960-
reverseMapping(tpr) match {
961-
case null => tpr
962-
case sym => sym.typeRef
963-
}
964-
case tv: TypeVar =>
965-
reverseMapping(tv.origin) match {
966-
case null => tv
967-
case sym => sym.typeRef
968-
}
969-
case _ =>
970-
(if (map != null) map else new TypeVarRemovingMap()).mapOver(tp)
971-
}
972-
private final class TypeVarRemovingMap(implicit ctx: Context) extends TypeMap {
973-
override def apply(tp: Type): Type = removeTypeVars(tp, this)
974-
}
975-
976-
private[this] var boundAdditionInProgress = false
977-
978-
// ---- Debug ------------------------------------------------------------
979-
980-
override def constr_println(msg: => String): Unit = gadtsConstr.println(msg)
981-
982-
override def debugBoundsDescription(implicit ctx: Context): String = {
983-
val sb = new mutable.StringBuilder
984-
sb ++= constraint.show
985-
sb += '\n'
986-
mapping.foreachBinding { case (sym, _) =>
987-
sb ++= i"$sym: ${bounds(sym)}\n"
988-
}
989-
sb.result
990-
}
991-
}
992-
993-
@sharable object EmptyGADTMap extends GADTMap {
994-
override def addEmptyBounds(sym: Symbol)(implicit ctx: Context): Unit = unsupported("EmptyGADTMap.addEmptyBounds")
995-
override def addBound(sym: Symbol, bound: Type, isUpper: Boolean)(implicit ctx: Context): Boolean = unsupported("EmptyGADTMap.addBound")
996-
override def bounds(sym: Symbol)(implicit ctx: Context): TypeBounds = null
997-
override def contains(sym: Symbol)(implicit ctx: Context) = false
998-
override def approximation(sym: Symbol, fromBelow: Boolean)(implicit ctx: Context): Type = unsupported("EmptyGADTMap.approximation")
999-
override def debugBoundsDescription(implicit ctx: Context): String = "EmptyGADTMap"
1000-
override def fresh = new SmartGADTMap
1001-
override def restore(other: GADTMap): Unit = {
1002-
if (!other.isEmpty) sys.error("cannot restore a non-empty GADTMap")
1003-
}
1004-
override def isEmpty: Boolean = true
1005-
}
1006777
}

0 commit comments

Comments
 (0)