Skip to content

Commit bd5c3b9

Browse files
committed
Rework variances of higher-kinded types
Decouple variances from type lambdas. A type lambda is just a function from types to types, it does not have a declared parameter variance. Of course, the variance of a type parameter can be determined by tracking occurrences of the parameter on the right hand side. That's a structural criterion, not a user-defined one. Intead of storing variances in type lambdas, store them in TypeBounds types. A non-alias type bound can have a declared parameter variance. The hope is that this change clarifies things conceptually and addresses tricky type inference problems that involve type aliases. First commit: Change TypeBounds data structures so that they can contain type parameter variances.
1 parent b96a026 commit bd5c3b9

File tree

4 files changed

+43
-5
lines changed

4 files changed

+43
-5
lines changed

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

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4251,6 +4251,14 @@ object Types {
42514251

42524252
override def underlying(implicit ctx: Context): Type = hi
42534253

4254+
/** Declared variances of type parameters, if bounds are type lambdas,
4255+
* or Nil if bounds are not lambdas or type parameters are non-variant.
4256+
*/
4257+
def variances: List[Int] = Nil
4258+
4259+
def withVariances(vs: List[Int])(implicit ctx: Context): TypeBounds =
4260+
if vs eq variances then this else TypeBounds(lo, hi, vs)
4261+
42544262
/** The non-alias type bounds type with given bounds */
42554263
def derivedTypeBounds(lo: Type, hi: Type)(implicit ctx: Context): TypeBounds =
42564264
if ((lo eq this.lo) && (hi eq this.hi)) this
@@ -4296,19 +4304,41 @@ object Types {
42964304

42974305
override def iso(that: Any, bs: BinderPairs): Boolean = that match {
42984306
case that: AliasingBounds => false
4299-
case that: TypeBounds => lo.equals(that.lo, bs) && hi.equals(that.hi, bs)
4307+
case that: TypeBounds => lo.equals(that.lo, bs) && hi.equals(that.hi, bs) && that.variances.isEmpty
43004308
case _ => false
43014309
}
43024310

43034311
override def eql(that: Type): Boolean = that match {
43044312
case that: AliasingBounds => false
4305-
case that: TypeBounds => lo.eq(that.lo) && hi.eq(that.hi)
4313+
case that: TypeBounds => lo.eq(that.lo) && hi.eq(that.hi) && that.variances.isEmpty
43064314
case _ => false
43074315
}
43084316
}
43094317

43104318
class RealTypeBounds(lo: Type, hi: Type) extends TypeBounds(lo, hi)
43114319

4320+
class VariantTypeBounds(lo: Type, hi: Type, variances: List[Int] = Nil) extends RealTypeBounds(lo, hi) {
4321+
4322+
/** The non-alias type bounds type with given bounds */
4323+
override def derivedTypeBounds(lo: Type, hi: Type)(implicit ctx: Context): TypeBounds =
4324+
if (lo eq this.lo) && (hi eq this.hi) then this
4325+
else TypeBounds(lo, hi, variances)
4326+
4327+
override def computeHash(bs: Binders): Int = doHash(bs, variances, lo, hi)
4328+
4329+
override def iso(that: Any, bs: BinderPairs): Boolean = that match {
4330+
case that: AliasingBounds => false
4331+
case that: TypeBounds => lo.equals(that.lo, bs) && hi.equals(that.hi, bs) && that.variances == variances
4332+
case _ => false
4333+
}
4334+
4335+
override def eql(that: Type): Boolean = that match {
4336+
case that: AliasingBounds => false
4337+
case that: TypeBounds => lo.eq(that.lo) && hi.eq(that.hi) && that.variances == variances
4338+
case _ => false
4339+
}
4340+
}
4341+
43124342
/** Common supertype of `TypeAlias` and `MatchAlias` */
43134343
abstract class AliasingBounds(val alias: Type) extends TypeBounds(alias, alias) {
43144344

@@ -4352,6 +4382,9 @@ object Types {
43524382
object TypeBounds {
43534383
def apply(lo: Type, hi: Type)(implicit ctx: Context): TypeBounds =
43544384
unique(new RealTypeBounds(lo, hi))
4385+
def apply(lo: Type, hi: Type, variances: List[Int])(implicit ctx: Context): TypeBounds =
4386+
if variances.forall(_ == 0) then apply(lo, hi)
4387+
else unique(new VariantTypeBounds(lo, hi, variances))
43554388
def empty(implicit ctx: Context): TypeBounds = apply(defn.NothingType, defn.AnyType)
43564389
def upper(hi: Type)(implicit ctx: Context): TypeBounds = apply(defn.NothingType, hi)
43574390
def lower(lo: Type)(implicit ctx: Context): TypeBounds = apply(lo, defn.AnyType)

compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,11 @@ class TreePickler(pickler: TastyPickler) {
234234
pickleType(tpe.alias, richTypes)
235235
case tpe: TypeBounds =>
236236
writeByte(TYPEBOUNDS)
237-
withLength { pickleType(tpe.lo, richTypes); pickleType(tpe.hi, richTypes) }
237+
withLength {
238+
pickleType(tpe.lo, richTypes)
239+
pickleType(tpe.hi, richTypes)
240+
for v <- tpe.variances do writeNat(v + 1)
241+
}
238242
case tpe: AnnotatedType =>
239243
writeByte(ANNOTATEDtype)
240244
withLength { pickleType(tpe.parent, richTypes); pickleTree(tpe.annot.tree) }

compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ class TreeUnpickler(reader: TastyReader,
337337
val lo = readType()
338338
val hi = readType()
339339
if (lo.isMatch && (lo `eq` hi)) MatchAlias(lo)
340-
else TypeBounds(lo, hi)
340+
else TypeBounds(lo, hi).withVariances(until(end)(readNat() - 1))
341341
case ANNOTATEDtype =>
342342
AnnotatedType(readType(), Annotation(readTerm()))
343343
case ANDtype =>

tasty/src/dotty/tools/tasty/TastyFormat.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ Macro-format:
4646
// If positive, this is a NameRef for the fully qualified name of a term parameter.
4747
4848
NameRef = Nat // ordinal number of name in name table, starting from 1.
49+
Variance = Nat // 0 = contravariant, 1 = nonvariant, 2 = covariant
4950
5051
Note: Unqualified names in the name table are strings. The context decides whether a name is
5152
a type-name or a term-name. The same string can represent both.
@@ -154,7 +155,7 @@ Standard-Section: "ASTs" TopLevelStat*
154155
REFINEDtype Length underlying_Type refinement_NameRef info_Type -- underlying { refinement_name : info }
155156
APPLIEDtype Length tycon_Type arg_Type* -- tycon[args]
156157
TYPEALIAS alias_Type -- = alias
157-
TYPEBOUNDS Length low_Type high_Type -- >: low <: high
158+
TYPEBOUNDS Length low_Type high_Type Variance* -- >: low <: high, possibly with variances of parameters of lambdas in bounds
158159
ANNOTATEDtype Length underlying_Type annotation_Term -- underlying @ annotation
159160
ANDtype Length left_Type right_Type -- left & right
160161
ORtype Length left_Type right_Type -- lefgt | right

0 commit comments

Comments
 (0)