Skip to content

Commit df154be

Browse files
committed
Treat type lambdas structurally ...
... unless they are on the right hand side of type bounds or match aliases or they are type aliases where some variance is given explicitly with a `+` or `-`.
1 parent bf31b18 commit df154be

File tree

5 files changed

+59
-61
lines changed

5 files changed

+59
-61
lines changed

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

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,7 @@ object Types {
363363
case _ => false
364364
}
365365

366-
/** Is this a higher-kinded type lambda with given parameter variances? */
366+
/** Is this a higher-kinded type lambda with given parameter variances? */
367367
def isVariantLambda: Boolean = false
368368

369369
// ----- Higher-order combinators -----------------------------------
@@ -3571,22 +3571,30 @@ object Types {
35713571
* type T[X] = U becomes type T = [X] -> U
35723572
* type T[X] <: U becomes type T >: Nothing <: ([X] -> U)
35733573
* type T[X] >: L <: U becomes type T >: ([X] -> L) <: ([X] -> U)
3574+
*
3575+
* @param useVariances If true, set parameter variances of the type lambda to be as in `params`,
3576+
* If false, TypeBounds types and match aliases aways get their variances set
3577+
* and type aliases get variances set if some parameter is not invariant,
3578+
* but variances of parameters of other types are determined structurally,
3579+
* by looking how the parameter is used in the result type.
35743580
*/
35753581
def fromParams[PI <: ParamInfo.Of[TypeName]](params: List[PI], resultType: Type, useVariances: Boolean)(implicit ctx: Context): Type = {
35763582
def expand(tp: Type, useVariances: Boolean) =
3577-
if params.nonEmpty then
3583+
if params.nonEmpty && useVariances then
35783584
apply(params.map(_.paramName), params.map(_.paramVariance))(
35793585
tl => params.map(param => toPInfo(tl.integrate(params, param.paramInfo))),
35803586
tl => tl.integrate(params, tp))
35813587
else
35823588
super.fromParams(params, tp)
35833589
resultType match {
3584-
case rt: AliasingBounds =>
3585-
rt.derivedAlias(expand(rt.alias, useVariances))
3590+
case rt: MatchAlias =>
3591+
rt.derivedAlias(expand(rt.alias, true))
3592+
case rt: TypeAlias =>
3593+
rt.derivedAlias(expand(rt.alias, params.exists(!_.paramVariance.isEmpty)))
35863594
case rt @ TypeBounds(lo, hi) =>
35873595
rt.derivedTypeBounds(
3588-
if (lo.isRef(defn.NothingClass)) lo else expand(lo, false),
3589-
expand(hi, useVariances))
3596+
if (lo.isRef(defn.NothingClass)) lo else expand(lo, true),
3597+
expand(hi, true))
35903598
case rt =>
35913599
expand(rt, useVariances)
35923600
}

compiler/src/dotty/tools/dotc/transform/PostTyper.scala

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,10 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
250250
&& sym != defn.SourceFileAnnot
251251
then
252252
sym.addAnnotation(Annotation.makeSourceFile(ctx.compilationUnit.source.file.path))
253+
else (tree.rhs, sym.info) match
254+
case (rhs: LambdaTypeTree, bounds: TypeBounds) =>
255+
VarianceChecker.checkLambda(rhs, bounds)
256+
case _ =>
253257
processMemberDef(super.transform(tree))
254258
case tree: New if isCheckable(tree) =>
255259
Checking.checkInstantiable(tree.tpe, tree.posd)
@@ -279,9 +283,6 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
279283
case tpe => tpe
280284
}
281285
)
282-
case tree: LambdaTypeTree =>
283-
VarianceChecker.checkLambda(tree)
284-
super.transform(tree)
285286
case Import(expr, selectors) =>
286287
val exprTpe = expr.tpe
287288
val seen = mutable.Set.empty[Name]

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

Lines changed: 36 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -25,57 +25,46 @@ object VarianceChecker {
2525
* Note: this is achieved by a mechanism separate from checking class type parameters.
2626
* Question: Can the two mechanisms be combined in one?
2727
*/
28-
def checkLambda(tree: tpd.LambdaTypeTree)(implicit ctx: Context): Unit = {
29-
def checkType(tl: HKTypeLambda): Unit = {
30-
val checkOK = new TypeAccumulator[Boolean] {
31-
def paramVarianceSign(tref: TypeParamRef) =
32-
tl.typeParams(tref.paramNum).paramVarianceSign
33-
def error(tref: TypeParamRef) = {
34-
val paramName = tl.paramNames(tref.paramNum).toTermName
35-
val v = paramVarianceSign(tref)
36-
val paramVarianceStr = if (v == 0) "contra" else "co"
37-
val occursStr = variance match {
38-
case -1 => "contra"
39-
case 0 => "in"
40-
case 1 => "co"
28+
def checkLambda(tree: tpd.LambdaTypeTree, bounds: TypeBounds)(implicit ctx: Context): Unit =
29+
def checkType(tpe: Type): Unit = tpe match
30+
case tl: HKTypeLambda if tl.isVariantLambda =>
31+
val checkOK = new TypeAccumulator[Boolean] {
32+
def paramVarianceSign(tref: TypeParamRef) =
33+
tl.typeParams(tref.paramNum).paramVarianceSign
34+
def error(tref: TypeParamRef) = {
35+
val paramName = tl.paramNames(tref.paramNum).toTermName
36+
val v = paramVarianceSign(tref)
37+
val paramVarianceStr = if (v < 0) "contra" else "co"
38+
val occursStr = variance match {
39+
case -1 => "contra"
40+
case 0 => "in"
41+
case 1 => "co"
42+
}
43+
val pos = tree.tparams
44+
.find(_.name.toTermName == paramName)
45+
.map(_.sourcePos)
46+
.getOrElse(tree.sourcePos)
47+
ctx.error(em"${paramVarianceStr}variant type parameter $paramName occurs in ${occursStr}variant position in ${tl.resType}", pos)
4148
}
42-
val pos = tree.tparams
43-
.find(_.name.toTermName == paramName)
44-
.map(_.sourcePos)
45-
.getOrElse(tree.sourcePos)
46-
ctx.error(em"${paramVarianceStr}variant type parameter $paramName occurs in ${occursStr}variant position in ${tl.resType}", pos)
47-
}
48-
def apply(x: Boolean, t: Type) = x && {
49-
t match {
50-
case tref: TypeParamRef if tref.binder `eq` tl =>
51-
val v = paramVarianceSign(tref)
52-
varianceConforms(variance, v) || { error(tref); false }
53-
case AnnotatedType(_, annot) if annot.symbol == defn.UncheckedVarianceAnnot =>
54-
x
55-
case _ =>
56-
foldOver(x, t)
49+
def apply(x: Boolean, t: Type) = x && {
50+
t match {
51+
case tref: TypeParamRef if tref.binder `eq` tl =>
52+
val v = paramVarianceSign(tref)
53+
varianceConforms(variance, v) || { error(tref); false }
54+
case AnnotatedType(_, annot) if annot.symbol == defn.UncheckedVarianceAnnot =>
55+
x
56+
case _ =>
57+
foldOver(x, t)
58+
}
5759
}
5860
}
59-
}
60-
checkOK.apply(true, tl.resType)
61-
}
61+
checkOK(true, tl.resType)
62+
case _ =>
63+
end checkType
6264

63-
(tree.tpe: @unchecked) match {
64-
case tl: HKTypeLambda =>
65-
checkType(tl)
66-
// The type of a LambdaTypeTree can be a TypeBounds, see the documentation
67-
// of `LambdaTypeTree`.
68-
case TypeBounds(lo, hi: HKTypeLambda) =>
69-
// Can't assume that the lower bound is a type lambda, it could also be
70-
// a reference to `Nothing`.
71-
lo match {
72-
case lo: HKTypeLambda =>
73-
checkType(lo)
74-
case _ =>
75-
}
76-
checkType(hi)
77-
}
78-
}
65+
checkType(bounds.lo)
66+
checkType(bounds.hi)
67+
end checkLambda
7968
}
8069

8170
class VarianceChecker()(implicit ctx: Context) {

tests/neg/type-lambdas-posttyper.scala

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ object Test extends App {
1313
var x: X = init
1414
}
1515

16-
type TL3 = [+X] =>> Ref[X] // error: covariant type parameter X occurs in nonvariant position in Test.Ref[X]
16+
type TL3[+X] = Ref[X] // error: covariant type parameter X occurs in nonvariant position in Test.Ref[X]
1717
type TL4[-X] = X => X // error: contravariant type parameter X occurs in covariant position in X => X
1818

1919
def f[F <: [+X] =>> Any](x: F[String]): F[Any] = x
@@ -28,14 +28,14 @@ object Test extends App {
2828
type Neg3[-X] <: X // error
2929

3030
type Neg4 = [-X] =>> X // error
31-
type Neg5 >: [-X] =>> X <: [-X] =>> Any // error
32-
type Neg6 <: [-X] =>> X // error
31+
type Neg5[-X] >: X <: Any // error
32+
type Neg6[-X] <: X // error
3333

3434
type Pos1[+X] = Ref[X] // error
3535
type Pos2[+X] >: Ref[X] // error
3636
type Pos3[+X] <: Ref[X] // error
3737

3838
type Pos4 = [+X] =>> Ref[X] // error
39-
type Pos5 >: [+X] =>> Ref[X] <: [+X] =>> Any // error
40-
type Pos6 <: [+X] =>> Ref[X] // error
39+
type Pos5[+X] >: Ref[X] <: Any // error
40+
type Pos6[+X] <: Ref[X] // error
4141
}
File renamed without changes.

0 commit comments

Comments
 (0)