diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index 65e604d8fce0..6132a918241e 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -640,7 +640,29 @@ object Trees { def forwardTo: Tree[T] = tpt } - /** [typeparams] -> tpt */ + /** [typeparams] -> tpt + * + * Note: the type of such a tree is not necessarily a `HKTypeLambda`, it can + * also be a `TypeBounds` where the upper bound is an `HKTypeLambda`, and the + * lower bound is either a reference to `Nothing` or an `HKTypeLambda`, + * this happens because these trees are typed by `HKTypeLambda#fromParams` which + * makes sure to move bounds outside of the type lambda itself to simplify their + * handling in the compiler. + * + * You may ask: why not normalize the trees too? That way, + * + * LambdaTypeTree(X, TypeBoundsTree(A, B)) + * + * would become, + * + * TypeBoundsTree(LambdaTypeTree(X, A), LambdaTypeTree(X, B)) + * + * which would maintain consistency between a tree and its type. The problem + * with this definition is that the same tree `X` appears twice, therefore + * we'd have to create two symbols for it which makes it harder to relate the + * source code written by the user with the trees used by the compiler (for + * example, to make "find all references" work in the IDE). + */ case class LambdaTypeTree[-T >: Untyped] private[ast] (tparams: List[TypeDef[T]], body: Tree[T])(implicit @constructorOnly src: SourceFile) extends TypTree[T] { type ThisTree[-T >: Untyped] = LambdaTypeTree[T] diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 0538f704f777..d403aa1dd183 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -3358,9 +3358,9 @@ object Types { param.paramName.withVariance(param.paramVariance) /** Distributes Lambda inside type bounds. Examples: - * - * type T[X] = U becomes type T = [X] -> U - * type T[X] <: U becomes type T >: Nothign <: ([X] -> U) + * + * type T[X] = U becomes type T = [X] -> U + * type T[X] <: U becomes type T >: Nothing <: ([X] -> U) * type T[X] >: L <: U becomes type T >: ([X] -> L) <: ([X] -> U) */ override def fromParams[PI <: ParamInfo.Of[TypeName]](params: List[PI], resultType: Type)(implicit ctx: Context): Type = { diff --git a/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala b/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala index bdd1653d8e35..d76800241b8f 100644 --- a/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala +++ b/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala @@ -26,8 +26,8 @@ object VarianceChecker { * Note: this is achieved by a mechanism separate from checking class type parameters. * Question: Can the two mechanisms be combined in one? */ - def checkLambda(tree: tpd.LambdaTypeTree)(implicit ctx: Context): Unit = tree.tpe match { - case tl: HKTypeLambda => + def checkLambda(tree: tpd.LambdaTypeTree)(implicit ctx: Context): Unit = { + def checkType(tl: HKTypeLambda): Unit = { val checkOK = new TypeAccumulator[Boolean] { def error(tref: TypeParamRef) = { val VariantName(paramName, v) = tl.paramNames(tref.paramNum).toTermName @@ -56,7 +56,23 @@ object VarianceChecker { } } checkOK.apply(true, tl.resType) - case _ => + } + + (tree.tpe: @unchecked) match { + case tl: HKTypeLambda => + checkType(tl) + // The type of a LambdaTypeTree can be a TypeBounds, see the documentation + // of `LambdaTypeTree`. + case TypeBounds(lo, hi: HKTypeLambda) => + // Can't assume that the lower bound is a type lambda, it could also be + // a reference to `Nothing`. + lo match { + case lo: HKTypeLambda => + checkType(lo) + case _ => + } + checkType(hi) + } } } diff --git a/tests/neg/type-lambdas-posttyper.scala b/tests/neg/type-lambdas-posttyper.scala index d6ed328d883d..55fffd7e129b 100644 --- a/tests/neg/type-lambdas-posttyper.scala +++ b/tests/neg/type-lambdas-posttyper.scala @@ -23,4 +23,19 @@ object Test extends App { aref.x = 1 val s: String = sref.x + type Neg1[-X] = X // error + type Neg2[-X] >: X // error + type Neg3[-X] <: X // error + + type Neg4 = [-X] => X // error + type Neg5 >: [-X] => X <: [-X] => Any // error + type Neg6 <: [-X] => X // error + + type Pos1[+X] = Ref[X] // error + type Pos2[+X] >: Ref[X] // error + type Pos3[+X] <: Ref[X] // error + + type Pos4 = [+X] => Ref[X] // error + type Pos5 >: [+X] => Ref[X] <: [+X] => Any // error + type Pos6 <: [+X] => Ref[X] // error }