Skip to content

Rework variances of higher-kinded types #8082

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 32 commits into from
Jan 31, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
0d692ea
Implement base variance ops
odersky Jan 26, 2020
573c454
rename `paramVariance` -> `paramVarianceSign`
odersky Jan 26, 2020
8bfdc0b
Add infrastructure for setting paramVariances in HKTypeLambdas
odersky Jan 26, 2020
8a7abc7
Abstract over paramVariance instead of paramVarianceSign
odersky Jan 27, 2020
dcff28c
Don't encode variances in names
odersky Jan 27, 2020
d557ae1
Parse and print variances according to new scheme
odersky Jan 26, 2020
0eb65e5
Update kind projector check file
odersky Jan 23, 2020
ba42a23
Update repltest
odersky Jan 23, 2020
0847719
Update signature help test
odersky Jan 23, 2020
9fb77fd
Tasty pickling of variances in TypeBounds
odersky Jan 26, 2020
50ae719
Make useVariances tunable
odersky Jan 27, 2020
3bd90cd
Fix unpickling of type aliases
odersky Jan 27, 2020
91e167b
Blacklist 3 pickling tests
odersky Jan 27, 2020
39657ec
Switch to structural variances for type lambdas
odersky Jan 27, 2020
d983f4f
Drop VariantNames
odersky Jan 27, 2020
ef7bb36
Treat type lambdas structurally ...
odersky Jan 27, 2020
f358710
Don't treat opaque aliases structurally
odersky Jan 27, 2020
7c7bc58
Revert "Blacklist 3 pickling tests"
odersky Jan 27, 2020
ec293d9
Add fixed issue tests
odersky Jan 27, 2020
eef36c0
Update check files
odersky Jan 27, 2020
4ecf246
Drop adaptHkVariance
odersky Jan 27, 2020
5c9d041
Simplify EtaExpand
odersky Jan 28, 2020
d2a2fdd
Disallow variances in type lambdas
odersky Jan 28, 2020
86cb086
Further simplifications and test fixes
odersky Jan 28, 2020
da5ab63
Improve isRef queries against top types
odersky Jan 28, 2020
363a05c
Simplify pickling of type aliases
odersky Jan 28, 2020
bfa8579
Don't stop at LazyRefs when computing structural variances
odersky Jan 28, 2020
eb04b8f
Improve variance computation and printing
odersky Jan 29, 2020
8667689
Address review comments
odersky Jan 30, 2020
80c91ee
Add comment link to test
odersky Jan 30, 2020
22a7a81
Fix typos
odersky Jan 31, 2020
2396fe9
Remove stray comment line
odersky Jan 31, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,13 @@ object DesugarEnums {
val tparams = enumClass.typeParams
def isGround(tp: Type) = tp.subst(tparams, tparams.map(_ => NoType)) eq tp
val targs = tparams map { tparam =>
if (tparam.variance > 0 && isGround(tparam.info.bounds.lo))
if (tparam.is(Covariant) && isGround(tparam.info.bounds.lo))
tparam.info.bounds.lo
else if (tparam.variance < 0 && isGround(tparam.info.bounds.hi))
else if (tparam.is(Contravariant) && isGround(tparam.info.bounds.hi))
tparam.info.bounds.hi
else {
def problem =
if (tparam.variance == 0) "is non variant"
if (!tparam.isOneOf(VarianceFlags)) "is non variant"
else "has bounds that depend on a type parameter in the same parameter list"
errorType(i"""cannot determine type argument for enum parent $enumClass,
|type parameter $tparam $problem""", ctx.source.atSpan(span))
Expand Down
5 changes: 3 additions & 2 deletions compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala
Original file line number Diff line number Diff line change
Expand Up @@ -379,8 +379,9 @@ trait ConstraintHandling[AbstractContext] {
case bounds: TypeBounds =>
val lower = constraint.lower(param)
val upper = constraint.upper(param)
if (lower.nonEmpty && !bounds.lo.isRef(defn.NothingClass) ||
upper.nonEmpty && !bounds.hi.isRef(defn.AnyClass)) constr_println(i"INIT*** $tl")
if lower.nonEmpty && !bounds.lo.isRef(defn.NothingClass)
|| upper.nonEmpty && !bounds.hi.isAny
then constr_println(i"INIT*** $tl")
lower.forall(addOneBound(_, bounds.hi, isUpper = true)) &&
upper.forall(addOneBound(_, bounds.lo, isUpper = false))
case _ =>
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -535,7 +535,7 @@ class Definitions {
@tu lazy val StringModule: Symbol = StringClass.linkedClass
@tu lazy val String_+ : TermSymbol = enterMethod(StringClass, nme.raw.PLUS, methOfAny(StringType), Final)
@tu lazy val String_valueOf_Object: Symbol = StringModule.info.member(nme.valueOf).suchThat(_.info.firstParamTypes match {
case List(pt) => pt.isRef(AnyClass) || pt.isRef(ObjectClass)
case List(pt) => pt.isAny || pt.isAnyRef
case _ => false
}).symbol

Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/Denotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -775,7 +775,7 @@ object Denotations {

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

// ------ Transformations -----------------------------------------
Expand Down
22 changes: 2 additions & 20 deletions compiler/src/dotty/tools/dotc/core/GadtConstraint.scala
Original file line number Diff line number Diff line change
Expand Up @@ -155,26 +155,8 @@ final class ProperGadtConstraint private(
else if (isUpper) addLess(symTvar.origin, boundTvar.origin)
else addLess(boundTvar.origin, symTvar.origin)
case bound =>
val oldUpperBound = bounds(symTvar.origin)
// If we have bounds:
// F >: [t] => List[t] <: [t] => Any
// and we want to record that:
// F <: [+A] => List[A]
// we need to adapt the variance and instead record that:
// F <: [A] => List[A]
// We cannot record the original bound, since it is false that:
// [t] => List[t] <: [+A] => List[A]
//
// Note that the following code is accepted:
// class Foo[F[t] >: List[t]]
// type T = Foo[List]
// precisely because Foo[List] is desugared to Foo[[A] => List[A]].
//
// Ideally we'd adapt the bound in ConstraintHandling#addOneBound,
// but doing it there actually interferes with type inference.
val bound1 = bound.adaptHkVariances(oldUpperBound)
if (isUpper) addUpperBound(symTvar.origin, bound1)
else addLowerBound(symTvar.origin, bound1)
if (isUpper) addUpperBound(symTvar.origin, bound)
else addLowerBound(symTvar.origin, bound)
}
).reporting({
val descr = if (isUpper) "upper" else "lower"
Expand Down
6 changes: 3 additions & 3 deletions compiler/src/dotty/tools/dotc/core/Hashable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import scala.util.hashing.{ MurmurHash3 => hashing }
import annotation.tailrec

object Hashable {

/** A null terminated list of BindingTypes. We use `null` here for efficiency */
class Binders(val tp: BindingType, val next: Binders)

Expand Down Expand Up @@ -44,7 +44,7 @@ trait Hashable {
avoidSpecialHashes(hashing.finalizeHash(hashCode, arity))

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

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

Expand Down Expand Up @@ -80,7 +80,7 @@ trait Hashable {
finishHash(bs, hashing.mix(seed, elemHash), arity + 1, tps)
}


protected final def doHash(x: Any): Int =
finishHash(hashing.mix(hashSeed, x.hashCode), 1)

Expand Down
5 changes: 0 additions & 5 deletions compiler/src/dotty/tools/dotc/core/NameKinds.scala
Original file line number Diff line number Diff line change
Expand Up @@ -338,11 +338,6 @@ object NameKinds {
}
}

/** The kind of names that also encode a variance: 0 for contravariance, 1 for covariance. */
val VariantName: NumberedNameKind = new NumberedNameKind(VARIANT, "Variant") {
def mkString(underlying: TermName, info: ThisInfo) = "-+"(info.num).toString + underlying
}

/** Names of the form N_<outer>. Emitted by inliner, replaced by outer path
* in ExplicitOuter.
*/
Expand Down
24 changes: 0 additions & 24 deletions compiler/src/dotty/tools/dotc/core/NameOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -139,32 +139,8 @@ object NameOps {
name.replace { case ExpandedName(_, unexp) => unexp }
}

/** Remove the variance from the name. */
def invariantName: N = likeSpacedN {
name.replace { case VariantName(invariant, _) => invariant }
}

def errorName: N = likeSpacedN(name ++ nme.ERROR)

/** Map variance value -1, +1 to 0, 1 */
private def varianceToNat(v: Int) = (v + 1) / 2

/** Map 0, 1 to variance value -1, +1 */
private def natToVariance(n: Int) = n * 2 - 1

/** Name with variance prefix: `+` for covariant, `-` for contravariant */
def withVariance(v: Int): N = {
val underlying = name.exclude(VariantName)
likeSpacedN(
if (v == 0) underlying
else VariantName(underlying.toTermName, varianceToNat(v)))
}

/** The variance as implied by the variance prefix, or 0 if there is
* no variance prefix.
*/
def variance: Int = name.collect { case VariantName(_, n) => natToVariance(n) }.getOrElse(0)

def freshened(implicit ctx: Context): N = likeSpacedN {
name.toTermName match {
case ModuleClassName(original) => ModuleClassName(original.freshened)
Expand Down
1 change: 0 additions & 1 deletion compiler/src/dotty/tools/dotc/core/NameTags.scala
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ object NameTags extends TastyFormat.NameTags {
case TRAITSETTER => "TRAITSETTER"
case UNIQUE => "UNIQUE"
case DEFAULTGETTER => "DEFAULTGETTER"
case VARIANT => "VARIANT"
case OUTERSELECT => "OUTERSELECT"

case SUPERACCESSOR => "SUPERACCESSOR"
Expand Down
9 changes: 8 additions & 1 deletion compiler/src/dotty/tools/dotc/core/ParamInfo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package dotty.tools.dotc.core
import Names.Name
import Contexts.Context
import Types.Type
import Variances.{Variance, varianceToInt}

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

/** The variance of the type parameter */
def paramVariance(implicit ctx: Context): Int
def paramVariance(implicit ctx: Context): Variance

/** The variance of the type parameter, as a number -1, 0, +1.
* Bivariant is mapped to 1, i.e. it is treated like Covariant.
*/
final def paramVarianceSign(implicit ctx: Context): Int =
varianceToInt(paramVariance)

/** A type that refers to the parameter */
def paramRef(implicit ctx: Context): Type
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ trait PatternTypeConstrainer { self: TypeComparer =>
def apply(tp: Type) = mapOver(tp) match {
case tp @ AppliedType(tycon, args) =>
val args1 = args.zipWithConserve(tycon.typeParams)((arg, tparam) =>
if (tparam.paramVariance != 0) TypeBounds.empty else arg
if (tparam.paramVarianceSign != 0) TypeBounds.empty else arg
)
tp.derivedAppliedType(tycon, args1)
case tp =>
Expand Down
13 changes: 7 additions & 6 deletions compiler/src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import dotty.tools.io.AbstractFile
import Decorators.SymbolIteratorDecorator
import ast._
import Trees.Literal
import Variances.Variance
import annotation.tailrec
import util.SimpleIdentityMap
import util.Stats
Expand Down Expand Up @@ -1374,13 +1375,13 @@ object SymDenotations {
def namedType(implicit ctx: Context): NamedType =
if (isType) typeRef else termRef

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

/** The flags to be used for a type parameter owned by this symbol.
* Overridden by ClassDenotation.
Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/core/Symbols.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import ast.tpd
import tpd.{Tree, TreeProvider, TreeOps}
import ast.TreeTypeMap
import Constants.Constant
import Variances.{Variance, varianceFromInt}
import reporting.diagnostic.Message
import collection.mutable
import io.AbstractFile
Expand Down Expand Up @@ -699,7 +700,7 @@ object Symbols {
def paramInfo(implicit ctx: Context): Type = denot.info
def paramInfoAsSeenFrom(pre: Type)(implicit ctx: Context): Type = pre.memberInfo(this)
def paramInfoOrCompleter(implicit ctx: Context): Type = denot.infoOrCompleter
def paramVariance(implicit ctx: Context): Int = denot.variance
def paramVariance(implicit ctx: Context): Variance = denot.variance
def paramRef(implicit ctx: Context): TypeRef = denot.typeRef

// -------- Printing --------------------------------------------------------
Expand Down
83 changes: 3 additions & 80 deletions compiler/src/dotty/tools/dotc/core/TypeApplications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Decorators._
import util.Stats._
import Names._
import NameOps._
import Variances.variancesConform
import dotty.tools.dotc.config.Config

object TypeApplications {
Expand All @@ -22,24 +23,6 @@ object TypeApplications {
case _ => tp
}

/** Does variance `v1` conform to variance `v2`?
* This is the case if the variances are the same or `sym` is nonvariant.
*/
def varianceConforms(v1: Int, v2: Int): Boolean =
v1 == v2 || v2 == 0

/** Does the variance of type parameter `tparam1` conform to the variance of type parameter `tparam2`?
*/
def varianceConforms(tparam1: TypeParamInfo, tparam2: TypeParamInfo)(implicit ctx: Context): Boolean =
varianceConforms(tparam1.paramVariance, tparam2.paramVariance)

/** Do the variances of type parameters `tparams1` conform to the variances
* of corresponding type parameters `tparams2`?
* This is only the case of `tparams1` and `tparams2` have the same length.
*/
def variancesConform(tparams1: List[TypeParamInfo], tparams2: List[TypeParamInfo])(implicit ctx: Context): Boolean =
tparams1.corresponds(tparams2)(varianceConforms)

/** Extractor for
*
* [v1 X1: B1, ..., vn Xn: Bn] -> C[X1, ..., Xn]
Expand Down Expand Up @@ -156,8 +139,6 @@ class TypeApplications(val self: Type) extends AnyVal {
/** The type parameters of this type are:
* For a ClassInfo type, the type parameters of its class.
* For a typeref referring to a class, the type parameters of the class.
* For a typeref referring to a Lambda class, the type parameters of
* its right hand side or upper bound.
* For a refinement type, the type parameters of its parent, dropping
* any type parameter that is-rebound by the refinement.
*/
Expand Down Expand Up @@ -269,11 +250,9 @@ class TypeApplications(val self: Type) extends AnyVal {
/** Convert a type constructor `TC` which has type parameters `X1, ..., Xn`
* to `[X1, ..., Xn] -> TC[X1, ..., Xn]`.
*/
def EtaExpand(tparams: List[TypeSymbol])(implicit ctx: Context): Type = {
val tparamsToUse = if (variancesConform(typeParams, tparams)) tparams else typeParamSymbols
HKTypeLambda.fromParams(tparamsToUse, self.appliedTo(tparams.map(_.typeRef)))
def EtaExpand(tparams: List[TypeSymbol])(implicit ctx: Context): Type =
HKTypeLambda.fromParams(tparams, self.appliedTo(tparams.map(_.typeRef)))
//.ensuring(res => res.EtaReduce =:= self, s"res = $res, core = ${res.EtaReduce}, self = $self, hc = ${res.hashCode}")
}

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

/** If argument A and type parameter P are higher-kinded, adapt the variances
* of A to those of P, ensuring that the variances of the type lambda A
* agree with the variances of corresponding higher-kinded type parameters of P. Example:
*
* class GenericCompanion[+CC[X]]
* GenericCompanion[List]
*
* with adaptHkVariances, the argument `List` will expand to
*
* [X] => List[X]
*
* instead of
*
* [+X] => List[X]
*
* even though `List` is covariant. This adaptation is necessary to ignore conflicting
* variances in overriding members that have types of hk-type parameters such as
* `GenericCompanion[GenTraversable]` or `GenericCompanion[ListBuffer]`.
* When checking overriding, we need to validate the subtype relationship
*
* GenericCompanion[[X] -> ListBuffer[X]] <: GenericCompanion[[+X] -> GenTraversable[X]]
*
* Without adaptation, this would be false, and hence an overriding error would
* result. But with adaptation, the rhs argument will be adapted to
*
* [X] -> GenTraversable[X]
*
* which makes the subtype test succeed. The crucial point here is that, since
* GenericCompanion only expects a non-variant CC, the fact that GenTraversable
* is covariant is irrelevant, so can be ignored.
*/
def adaptHkVariances(bound: Type)(implicit ctx: Context): Type = {
val hkParams = bound.hkTypeParams
if (hkParams.isEmpty) self
else {
def adaptArg(arg: Type): Type = arg match {
case arg @ HKTypeLambda(tparams, body) if
!tparams.corresponds(hkParams)(_.paramVariance == _.paramVariance) &&
tparams.corresponds(hkParams)(varianceConforms) =>
HKTypeLambda(
tparams.lazyZip(hkParams).map((tparam, hkparam) =>
tparam.paramName.withVariance(hkparam.paramVariance)))(
tl => arg.paramInfos.map(_.subst(arg, tl).bounds),
tl => arg.resultType.subst(arg, tl)
)
case arg: AliasingBounds =>
arg.derivedAlias(adaptArg(arg.alias))
case arg @ TypeBounds(lo, hi) =>
arg.derivedTypeBounds(adaptArg(lo), adaptArg(hi))
case _ =>
arg
}
adaptArg(self)
}
}

/** The type representing
*
* T[U1, ..., Un]
Expand Down
Loading