Skip to content

Commit afb7a8b

Browse files
authored
Merge pull request #6318 from dotty-staging/fix-hkvariance
Fix #1252: Fix variance checking for parameterized typedefs
2 parents 874a203 + c97f505 commit afb7a8b

File tree

4 files changed

+60
-7
lines changed

4 files changed

+60
-7
lines changed

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

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -644,7 +644,29 @@ object Trees {
644644
def forwardTo: Tree[T] = tpt
645645
}
646646

647-
/** [typeparams] -> tpt */
647+
/** [typeparams] -> tpt
648+
*
649+
* Note: the type of such a tree is not necessarily a `HKTypeLambda`, it can
650+
* also be a `TypeBounds` where the upper bound is an `HKTypeLambda`, and the
651+
* lower bound is either a reference to `Nothing` or an `HKTypeLambda`,
652+
* this happens because these trees are typed by `HKTypeLambda#fromParams` which
653+
* makes sure to move bounds outside of the type lambda itself to simplify their
654+
* handling in the compiler.
655+
*
656+
* You may ask: why not normalize the trees too? That way,
657+
*
658+
* LambdaTypeTree(X, TypeBoundsTree(A, B))
659+
*
660+
* would become,
661+
*
662+
* TypeBoundsTree(LambdaTypeTree(X, A), LambdaTypeTree(X, B))
663+
*
664+
* which would maintain consistency between a tree and its type. The problem
665+
* with this definition is that the same tree `X` appears twice, therefore
666+
* we'd have to create two symbols for it which makes it harder to relate the
667+
* source code written by the user with the trees used by the compiler (for
668+
* example, to make "find all references" work in the IDE).
669+
*/
648670
case class LambdaTypeTree[-T >: Untyped] private[ast] (tparams: List[TypeDef[T]], body: Tree[T])(implicit @constructorOnly src: SourceFile)
649671
extends TypTree[T] {
650672
type ThisTree[-T >: Untyped] = LambdaTypeTree[T]

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3358,9 +3358,9 @@ object Types {
33583358
param.paramName.withVariance(param.paramVariance)
33593359

33603360
/** Distributes Lambda inside type bounds. Examples:
3361-
*
3362-
* type T[X] = U becomes type T = [X] -> U
3363-
* type T[X] <: U becomes type T >: Nothign <: ([X] -> U)
3361+
*
3362+
* type T[X] = U becomes type T = [X] -> U
3363+
* type T[X] <: U becomes type T >: Nothing <: ([X] -> U)
33643364
* type T[X] >: L <: U becomes type T >: ([X] -> L) <: ([X] -> U)
33653365
*/
33663366
override def fromParams[PI <: ParamInfo.Of[TypeName]](params: List[PI], resultType: Type)(implicit ctx: Context): Type = {

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

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ object VarianceChecker {
2626
* Note: this is achieved by a mechanism separate from checking class type parameters.
2727
* Question: Can the two mechanisms be combined in one?
2828
*/
29-
def checkLambda(tree: tpd.LambdaTypeTree)(implicit ctx: Context): Unit = tree.tpe match {
30-
case tl: HKTypeLambda =>
29+
def checkLambda(tree: tpd.LambdaTypeTree)(implicit ctx: Context): Unit = {
30+
def checkType(tl: HKTypeLambda): Unit = {
3131
val checkOK = new TypeAccumulator[Boolean] {
3232
def error(tref: TypeParamRef) = {
3333
val VariantName(paramName, v) = tl.paramNames(tref.paramNum).toTermName
@@ -56,7 +56,23 @@ object VarianceChecker {
5656
}
5757
}
5858
checkOK.apply(true, tl.resType)
59-
case _ =>
59+
}
60+
61+
(tree.tpe: @unchecked) match {
62+
case tl: HKTypeLambda =>
63+
checkType(tl)
64+
// The type of a LambdaTypeTree can be a TypeBounds, see the documentation
65+
// of `LambdaTypeTree`.
66+
case TypeBounds(lo, hi: HKTypeLambda) =>
67+
// Can't assume that the lower bound is a type lambda, it could also be
68+
// a reference to `Nothing`.
69+
lo match {
70+
case lo: HKTypeLambda =>
71+
checkType(lo)
72+
case _ =>
73+
}
74+
checkType(hi)
75+
}
6076
}
6177
}
6278

tests/neg/type-lambdas-posttyper.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,19 @@ object Test extends App {
2323
aref.x = 1
2424
val s: String = sref.x
2525

26+
type Neg1[-X] = X // error
27+
type Neg2[-X] >: X // error
28+
type Neg3[-X] <: X // error
29+
30+
type Neg4 = [-X] => X // error
31+
type Neg5 >: [-X] => X <: [-X] => Any // error
32+
type Neg6 <: [-X] => X // error
33+
34+
type Pos1[+X] = Ref[X] // error
35+
type Pos2[+X] >: Ref[X] // error
36+
type Pos3[+X] <: Ref[X] // error
37+
38+
type Pos4 = [+X] => Ref[X] // error
39+
type Pos5 >: [+X] => Ref[X] <: [+X] => Any // error
40+
type Pos6 <: [+X] => Ref[X] // error
2641
}

0 commit comments

Comments
 (0)