Skip to content

Commit f833baa

Browse files
authored
Merge pull request #8082 from dotty-staging/change-lambda-variance
Rework variances of higher-kinded types
2 parents 23affe1 + 2396fe9 commit f833baa

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+653
-486
lines changed

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,13 @@ object DesugarEnums {
4949
val tparams = enumClass.typeParams
5050
def isGround(tp: Type) = tp.subst(tparams, tparams.map(_ => NoType)) eq tp
5151
val targs = tparams map { tparam =>
52-
if (tparam.variance > 0 && isGround(tparam.info.bounds.lo))
52+
if (tparam.is(Covariant) && isGround(tparam.info.bounds.lo))
5353
tparam.info.bounds.lo
54-
else if (tparam.variance < 0 && isGround(tparam.info.bounds.hi))
54+
else if (tparam.is(Contravariant) && isGround(tparam.info.bounds.hi))
5555
tparam.info.bounds.hi
5656
else {
5757
def problem =
58-
if (tparam.variance == 0) "is non variant"
58+
if (!tparam.isOneOf(VarianceFlags)) "is non variant"
5959
else "has bounds that depend on a type parameter in the same parameter list"
6060
errorType(i"""cannot determine type argument for enum parent $enumClass,
6161
|type parameter $tparam $problem""", ctx.source.atSpan(span))

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -379,8 +379,9 @@ trait ConstraintHandling[AbstractContext] {
379379
case bounds: TypeBounds =>
380380
val lower = constraint.lower(param)
381381
val upper = constraint.upper(param)
382-
if (lower.nonEmpty && !bounds.lo.isRef(defn.NothingClass) ||
383-
upper.nonEmpty && !bounds.hi.isRef(defn.AnyClass)) constr_println(i"INIT*** $tl")
382+
if lower.nonEmpty && !bounds.lo.isRef(defn.NothingClass)
383+
|| upper.nonEmpty && !bounds.hi.isAny
384+
then constr_println(i"INIT*** $tl")
384385
lower.forall(addOneBound(_, bounds.hi, isUpper = true)) &&
385386
upper.forall(addOneBound(_, bounds.lo, isUpper = false))
386387
case _ =>

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -535,7 +535,7 @@ class Definitions {
535535
@tu lazy val StringModule: Symbol = StringClass.linkedClass
536536
@tu lazy val String_+ : TermSymbol = enterMethod(StringClass, nme.raw.PLUS, methOfAny(StringType), Final)
537537
@tu lazy val String_valueOf_Object: Symbol = StringModule.info.member(nme.valueOf).suchThat(_.info.firstParamTypes match {
538-
case List(pt) => pt.isRef(AnyClass) || pt.isRef(ObjectClass)
538+
case List(pt) => pt.isAny || pt.isAnyRef
539539
case _ => false
540540
}).symbol
541541

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -775,7 +775,7 @@ object Denotations {
775775

776776
def matchesImportBound(bound: Type)(implicit ctx: Context): Boolean =
777777
if bound.isRef(defn.NothingClass) then false
778-
else if bound.isRef(defn.AnyClass) then true
778+
else if bound.isAny then true
779779
else NoViewsAllowed.normalizedCompatible(info, bound, keepConstraint = false)
780780

781781
// ------ Transformations -----------------------------------------

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

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -155,26 +155,8 @@ final class ProperGadtConstraint private(
155155
else if (isUpper) addLess(symTvar.origin, boundTvar.origin)
156156
else addLess(boundTvar.origin, symTvar.origin)
157157
case bound =>
158-
val oldUpperBound = bounds(symTvar.origin)
159-
// If we have bounds:
160-
// F >: [t] => List[t] <: [t] => Any
161-
// and we want to record that:
162-
// F <: [+A] => List[A]
163-
// we need to adapt the variance and instead record that:
164-
// F <: [A] => List[A]
165-
// We cannot record the original bound, since it is false that:
166-
// [t] => List[t] <: [+A] => List[A]
167-
//
168-
// Note that the following code is accepted:
169-
// class Foo[F[t] >: List[t]]
170-
// type T = Foo[List]
171-
// precisely because Foo[List] is desugared to Foo[[A] => List[A]].
172-
//
173-
// Ideally we'd adapt the bound in ConstraintHandling#addOneBound,
174-
// but doing it there actually interferes with type inference.
175-
val bound1 = bound.adaptHkVariances(oldUpperBound)
176-
if (isUpper) addUpperBound(symTvar.origin, bound1)
177-
else addLowerBound(symTvar.origin, bound1)
158+
if (isUpper) addUpperBound(symTvar.origin, bound)
159+
else addLowerBound(symTvar.origin, bound)
178160
}
179161
).reporting({
180162
val descr = if (isUpper) "upper" else "lower"

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import scala.util.hashing.{ MurmurHash3 => hashing }
66
import annotation.tailrec
77

88
object Hashable {
9-
9+
1010
/** A null terminated list of BindingTypes. We use `null` here for efficiency */
1111
class Binders(val tp: BindingType, val next: Binders)
1212

@@ -44,7 +44,7 @@ trait Hashable {
4444
avoidSpecialHashes(hashing.finalizeHash(hashCode, arity))
4545

4646
final def typeHash(bs: Binders, tp: Type): Int =
47-
if (bs == null || tp.stableHash) tp.hash else tp.computeHash(bs)
47+
if (bs == null || tp.hashIsStable) tp.hash else tp.computeHash(bs)
4848

4949
def identityHash(bs: Binders): Int = avoidSpecialHashes(System.identityHashCode(this))
5050

@@ -80,7 +80,7 @@ trait Hashable {
8080
finishHash(bs, hashing.mix(seed, elemHash), arity + 1, tps)
8181
}
8282

83-
83+
8484
protected final def doHash(x: Any): Int =
8585
finishHash(hashing.mix(hashSeed, x.hashCode), 1)
8686

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

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -338,11 +338,6 @@ object NameKinds {
338338
}
339339
}
340340

341-
/** The kind of names that also encode a variance: 0 for contravariance, 1 for covariance. */
342-
val VariantName: NumberedNameKind = new NumberedNameKind(VARIANT, "Variant") {
343-
def mkString(underlying: TermName, info: ThisInfo) = "-+"(info.num).toString + underlying
344-
}
345-
346341
/** Names of the form N_<outer>. Emitted by inliner, replaced by outer path
347342
* in ExplicitOuter.
348343
*/

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

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -139,32 +139,8 @@ object NameOps {
139139
name.replace { case ExpandedName(_, unexp) => unexp }
140140
}
141141

142-
/** Remove the variance from the name. */
143-
def invariantName: N = likeSpacedN {
144-
name.replace { case VariantName(invariant, _) => invariant }
145-
}
146-
147142
def errorName: N = likeSpacedN(name ++ nme.ERROR)
148143

149-
/** Map variance value -1, +1 to 0, 1 */
150-
private def varianceToNat(v: Int) = (v + 1) / 2
151-
152-
/** Map 0, 1 to variance value -1, +1 */
153-
private def natToVariance(n: Int) = n * 2 - 1
154-
155-
/** Name with variance prefix: `+` for covariant, `-` for contravariant */
156-
def withVariance(v: Int): N = {
157-
val underlying = name.exclude(VariantName)
158-
likeSpacedN(
159-
if (v == 0) underlying
160-
else VariantName(underlying.toTermName, varianceToNat(v)))
161-
}
162-
163-
/** The variance as implied by the variance prefix, or 0 if there is
164-
* no variance prefix.
165-
*/
166-
def variance: Int = name.collect { case VariantName(_, n) => natToVariance(n) }.getOrElse(0)
167-
168144
def freshened(implicit ctx: Context): N = likeSpacedN {
169145
name.toTermName match {
170146
case ModuleClassName(original) => ModuleClassName(original.freshened)

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ object NameTags extends TastyFormat.NameTags {
4545
case TRAITSETTER => "TRAITSETTER"
4646
case UNIQUE => "UNIQUE"
4747
case DEFAULTGETTER => "DEFAULTGETTER"
48-
case VARIANT => "VARIANT"
4948
case OUTERSELECT => "OUTERSELECT"
5049

5150
case SUPERACCESSOR => "SUPERACCESSOR"

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package dotty.tools.dotc.core
33
import Names.Name
44
import Contexts.Context
55
import Types.Type
6+
import Variances.{Variance, varianceToInt}
67

78
/** A common super trait of Symbol and LambdaParam.
89
* Used to capture the attributes of type parameters which can be implemented as either.
@@ -35,7 +36,13 @@ trait ParamInfo {
3536
def paramInfoOrCompleter(implicit ctx: Context): Type
3637

3738
/** The variance of the type parameter */
38-
def paramVariance(implicit ctx: Context): Int
39+
def paramVariance(implicit ctx: Context): Variance
40+
41+
/** The variance of the type parameter, as a number -1, 0, +1.
42+
* Bivariant is mapped to 1, i.e. it is treated like Covariant.
43+
*/
44+
final def paramVarianceSign(implicit ctx: Context): Int =
45+
varianceToInt(paramVariance)
3946

4047
/** A type that refers to the parameter */
4148
def paramRef(implicit ctx: Context): Type

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ trait PatternTypeConstrainer { self: TypeComparer =>
191191
def apply(tp: Type) = mapOver(tp) match {
192192
case tp @ AppliedType(tycon, args) =>
193193
val args1 = args.zipWithConserve(tycon.typeParams)((arg, tparam) =>
194-
if (tparam.paramVariance != 0) TypeBounds.empty else arg
194+
if (tparam.paramVarianceSign != 0) TypeBounds.empty else arg
195195
)
196196
tp.derivedAppliedType(tycon, args1)
197197
case tp =>

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

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import dotty.tools.io.AbstractFile
1212
import Decorators.SymbolIteratorDecorator
1313
import ast._
1414
import Trees.Literal
15+
import Variances.Variance
1516
import annotation.tailrec
1617
import util.SimpleIdentityMap
1718
import util.Stats
@@ -1371,13 +1372,13 @@ object SymDenotations {
13711372
def namedType(implicit ctx: Context): NamedType =
13721373
if (isType) typeRef else termRef
13731374

1374-
/** The variance of this type parameter or type member as an Int, with
1375-
* +1 = Covariant, -1 = Contravariant, 0 = Nonvariant, or not a type parameter
1375+
/** The variance of this type parameter or type member as a subset of
1376+
* {Covariant, Contravariant}
13761377
*/
1377-
final def variance(implicit ctx: Context): Int =
1378-
if (this.is(Covariant)) 1
1379-
else if (this.is(Contravariant)) -1
1380-
else 0
1378+
final def variance(implicit ctx: Context): Variance =
1379+
if is(Covariant) then Covariant
1380+
else if is(Contravariant) then Contravariant
1381+
else EmptyFlags
13811382

13821383
/** The flags to be used for a type parameter owned by this symbol.
13831384
* Overridden by ClassDenotation.

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import ast.tpd
2222
import tpd.{Tree, TreeProvider, TreeOps}
2323
import ast.TreeTypeMap
2424
import Constants.Constant
25+
import Variances.{Variance, varianceFromInt}
2526
import reporting.diagnostic.Message
2627
import collection.mutable
2728
import io.AbstractFile
@@ -699,7 +700,7 @@ object Symbols {
699700
def paramInfo(implicit ctx: Context): Type = denot.info
700701
def paramInfoAsSeenFrom(pre: Type)(implicit ctx: Context): Type = pre.memberInfo(this)
701702
def paramInfoOrCompleter(implicit ctx: Context): Type = denot.infoOrCompleter
702-
def paramVariance(implicit ctx: Context): Int = denot.variance
703+
def paramVariance(implicit ctx: Context): Variance = denot.variance
703704
def paramRef(implicit ctx: Context): TypeRef = denot.typeRef
704705

705706
// -------- Printing --------------------------------------------------------

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

Lines changed: 3 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import Decorators._
1010
import util.Stats._
1111
import Names._
1212
import NameOps._
13+
import Variances.variancesConform
1314
import dotty.tools.dotc.config.Config
1415

1516
object TypeApplications {
@@ -22,24 +23,6 @@ object TypeApplications {
2223
case _ => tp
2324
}
2425

25-
/** Does variance `v1` conform to variance `v2`?
26-
* This is the case if the variances are the same or `sym` is nonvariant.
27-
*/
28-
def varianceConforms(v1: Int, v2: Int): Boolean =
29-
v1 == v2 || v2 == 0
30-
31-
/** Does the variance of type parameter `tparam1` conform to the variance of type parameter `tparam2`?
32-
*/
33-
def varianceConforms(tparam1: TypeParamInfo, tparam2: TypeParamInfo)(implicit ctx: Context): Boolean =
34-
varianceConforms(tparam1.paramVariance, tparam2.paramVariance)
35-
36-
/** Do the variances of type parameters `tparams1` conform to the variances
37-
* of corresponding type parameters `tparams2`?
38-
* This is only the case of `tparams1` and `tparams2` have the same length.
39-
*/
40-
def variancesConform(tparams1: List[TypeParamInfo], tparams2: List[TypeParamInfo])(implicit ctx: Context): Boolean =
41-
tparams1.corresponds(tparams2)(varianceConforms)
42-
4326
/** Extractor for
4427
*
4528
* [v1 X1: B1, ..., vn Xn: Bn] -> C[X1, ..., Xn]
@@ -156,8 +139,6 @@ class TypeApplications(val self: Type) extends AnyVal {
156139
/** The type parameters of this type are:
157140
* For a ClassInfo type, the type parameters of its class.
158141
* For a typeref referring to a class, the type parameters of the class.
159-
* For a typeref referring to a Lambda class, the type parameters of
160-
* its right hand side or upper bound.
161142
* For a refinement type, the type parameters of its parent, dropping
162143
* any type parameter that is-rebound by the refinement.
163144
*/
@@ -269,11 +250,9 @@ class TypeApplications(val self: Type) extends AnyVal {
269250
/** Convert a type constructor `TC` which has type parameters `X1, ..., Xn`
270251
* to `[X1, ..., Xn] -> TC[X1, ..., Xn]`.
271252
*/
272-
def EtaExpand(tparams: List[TypeSymbol])(implicit ctx: Context): Type = {
273-
val tparamsToUse = if (variancesConform(typeParams, tparams)) tparams else typeParamSymbols
274-
HKTypeLambda.fromParams(tparamsToUse, self.appliedTo(tparams.map(_.typeRef)))
253+
def EtaExpand(tparams: List[TypeSymbol])(implicit ctx: Context): Type =
254+
HKTypeLambda.fromParams(tparams, self.appliedTo(tparams.map(_.typeRef)))
275255
//.ensuring(res => res.EtaReduce =:= self, s"res = $res, core = ${res.EtaReduce}, self = $self, hc = ${res.hashCode}")
276-
}
277256

278257
/** If self is not lambda-bound, eta expand it. */
279258
def ensureLambdaSub(implicit ctx: Context): Type =
@@ -290,62 +269,6 @@ class TypeApplications(val self: Type) extends AnyVal {
290269
}
291270
}
292271

293-
/** If argument A and type parameter P are higher-kinded, adapt the variances
294-
* of A to those of P, ensuring that the variances of the type lambda A
295-
* agree with the variances of corresponding higher-kinded type parameters of P. Example:
296-
*
297-
* class GenericCompanion[+CC[X]]
298-
* GenericCompanion[List]
299-
*
300-
* with adaptHkVariances, the argument `List` will expand to
301-
*
302-
* [X] => List[X]
303-
*
304-
* instead of
305-
*
306-
* [+X] => List[X]
307-
*
308-
* even though `List` is covariant. This adaptation is necessary to ignore conflicting
309-
* variances in overriding members that have types of hk-type parameters such as
310-
* `GenericCompanion[GenTraversable]` or `GenericCompanion[ListBuffer]`.
311-
* When checking overriding, we need to validate the subtype relationship
312-
*
313-
* GenericCompanion[[X] -> ListBuffer[X]] <: GenericCompanion[[+X] -> GenTraversable[X]]
314-
*
315-
* Without adaptation, this would be false, and hence an overriding error would
316-
* result. But with adaptation, the rhs argument will be adapted to
317-
*
318-
* [X] -> GenTraversable[X]
319-
*
320-
* which makes the subtype test succeed. The crucial point here is that, since
321-
* GenericCompanion only expects a non-variant CC, the fact that GenTraversable
322-
* is covariant is irrelevant, so can be ignored.
323-
*/
324-
def adaptHkVariances(bound: Type)(implicit ctx: Context): Type = {
325-
val hkParams = bound.hkTypeParams
326-
if (hkParams.isEmpty) self
327-
else {
328-
def adaptArg(arg: Type): Type = arg match {
329-
case arg @ HKTypeLambda(tparams, body) if
330-
!tparams.corresponds(hkParams)(_.paramVariance == _.paramVariance) &&
331-
tparams.corresponds(hkParams)(varianceConforms) =>
332-
HKTypeLambda(
333-
tparams.lazyZip(hkParams).map((tparam, hkparam) =>
334-
tparam.paramName.withVariance(hkparam.paramVariance)))(
335-
tl => arg.paramInfos.map(_.subst(arg, tl).bounds),
336-
tl => arg.resultType.subst(arg, tl)
337-
)
338-
case arg: AliasingBounds =>
339-
arg.derivedAlias(adaptArg(arg.alias))
340-
case arg @ TypeBounds(lo, hi) =>
341-
arg.derivedTypeBounds(adaptArg(lo), adaptArg(hi))
342-
case _ =>
343-
arg
344-
}
345-
adaptArg(self)
346-
}
347-
}
348-
349272
/** The type representing
350273
*
351274
* T[U1, ..., Un]

0 commit comments

Comments
 (0)