Skip to content

Commit f194a86

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 5a86c1f commit f194a86

File tree

5 files changed

+70
-10
lines changed

5 files changed

+70
-10
lines changed

compiler/src/dotty/tools/dotc/ast/Trees.scala

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -694,6 +694,19 @@ object Trees {
694694
case class TypeBoundsTree[-T >: Untyped] private[ast] (lo: Tree[T], hi: Tree[T])(implicit @constructorOnly src: SourceFile)
695695
extends TypTree[T] {
696696
type ThisTree[-T >: Untyped] = TypeBoundsTree[T]
697+
698+
/** Declared variances of type parameters, if bounds are type lambdas,
699+
* or Nil if bounds are not lambdas or type parameters are non-variant.
700+
*/
701+
def variances: List[Int] = Nil
702+
}
703+
704+
/** A (higher-kinded) type bounds with given variances for the type parameters */
705+
class VariantTypeBoundsTree[-T >: Untyped] private[ast]
706+
(lo: Tree[T], hi: Tree[T], override val variances: List[Int] = Nil)
707+
(implicit @constructorOnly src: SourceFile)
708+
extends TypeBoundsTree[T](lo, hi) {
709+
override def toString: String = s"VariantTypeBoundsTree($lo, $hi, ${variances.mkString(", ")})"
697710
}
698711

699712
/** name @ body */
@@ -975,6 +988,7 @@ object Trees {
975988
type MatchTypeTree = Trees.MatchTypeTree[T]
976989
type ByNameTypeTree = Trees.ByNameTypeTree[T]
977990
type TypeBoundsTree = Trees.TypeBoundsTree[T]
991+
type VariantTypeBoundsTree = Trees.VariantTypeBoundsTree[T]
978992
type Bind = Trees.Bind[T]
979993
type Alternative = Trees.Alternative[T]
980994
type UnApply = Trees.UnApply[T]
@@ -1001,6 +1015,9 @@ object Trees {
10011015
case ys => Thicket(ys)
10021016
}
10031017

1018+
def (tree: TypeBoundsTree).withVariances(vs: List[Int])(implicit src: SourceFile): TypeBoundsTree =
1019+
if vs.forall(_ == 0) then tree else new VariantTypeBoundsTree(tree.lo, tree.hi, vs)
1020+
10041021
// ----- Helper classes for copying, transforming, accumulating -----------------
10051022

10061023
val cpy: TreeCopier
@@ -1152,6 +1169,9 @@ object Trees {
11521169
case _ => finalize(tree, untpd.ByNameTypeTree(result)(sourceFile(tree)))
11531170
}
11541171
def TypeBoundsTree(tree: Tree)(lo: Tree, hi: Tree)(implicit ctx: Context): TypeBoundsTree = tree match {
1172+
case tree: VariantTypeBoundsTree =>
1173+
if (lo eq tree.lo) && (hi eq tree.hi) then tree
1174+
else finalize(tree, VariantTypeBoundsTree(lo, hi, tree.variances))
11551175
case tree: TypeBoundsTree if (lo eq tree.lo) && (hi eq tree.hi) => tree
11561176
case _ => finalize(tree, untpd.TypeBoundsTree(lo, hi)(sourceFile(tree)))
11571177
}

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: 10 additions & 4 deletions
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) }
@@ -577,11 +581,13 @@ class TreePickler(pickler: TastyPickler) {
577581
case LambdaTypeTree(tparams, body) =>
578582
writeByte(LAMBDAtpt)
579583
withLength { pickleParams(tparams); pickleTree(body) }
580-
case TypeBoundsTree(lo, hi) =>
584+
case tree @ TypeBoundsTree(lo, hi) =>
581585
writeByte(TYPEBOUNDStpt)
582586
withLength {
583-
pickleTree(lo);
584-
if (hi ne lo) pickleTree(hi)
587+
pickleTree(lo)
588+
if hi ne lo then
589+
pickleTree(hi)
590+
for v <- tree.variances do writeNat(v + 1)
585591
}
586592
case Hole(_, idx, args) =>
587593
writeByte(HOLE)

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

Lines changed: 2 additions & 2 deletions
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 =>
@@ -1199,7 +1199,7 @@ class TreeUnpickler(reader: TastyReader,
11991199
case TYPEBOUNDStpt =>
12001200
val lo = readTpt()
12011201
val hi = if (currentAddr == end) lo else readTpt()
1202-
TypeBoundsTree(lo, hi)
1202+
TypeBoundsTree(lo, hi).withVariances(until(end)(readNat() - 1))
12031203
case HOLE =>
12041204
readHole(end, isType = false)
12051205
case _ =>

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

Lines changed: 3 additions & 2 deletions
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.
@@ -106,7 +107,7 @@ Standard-Section: "ASTs" TopLevelStat*
106107
REFINEDtpt Length underlying_Term refinement_Stat* -- underlying {refinements}
107108
APPLIEDtpt Length tycon_Term arg_Term* -- tycon [args]
108109
LAMBDAtpt Length TypeParam* body_Term -- [TypeParams] => body
109-
TYPEBOUNDStpt Length low_Term high_Term? -- >: low <: high
110+
TYPEBOUNDStpt Length low_Term high_Term? Variance* -- >: low <: high, possibly with variances of parameters of lambdas in bounds
110111
ANNOTATEDtpt Length underlying_Term fullAnnotation_Term -- underlying @ annotation
111112
MATCHtpt Length bound_Term? sel_Term CaseDef* -- sel match { CaseDef } where `bound` is optional upper bound of all rhs
112113
BYNAMEtpt underlying_Term -- => underlying
@@ -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)