diff options
Diffstat (limited to 'src/dotty/tools/dotc/core')
-rw-r--r-- | src/dotty/tools/dotc/core/ConstraintHandling.scala | 10 | ||||
-rw-r--r-- | src/dotty/tools/dotc/core/Definitions.scala | 16 | ||||
-rw-r--r-- | src/dotty/tools/dotc/core/Denotations.scala | 2 | ||||
-rw-r--r-- | src/dotty/tools/dotc/core/Flags.scala | 15 | ||||
-rw-r--r-- | src/dotty/tools/dotc/core/NameOps.scala | 16 | ||||
-rw-r--r-- | src/dotty/tools/dotc/core/StdNames.scala | 20 | ||||
-rw-r--r-- | src/dotty/tools/dotc/core/SymDenotations.scala | 3 | ||||
-rw-r--r-- | src/dotty/tools/dotc/core/Symbols.scala | 2 | ||||
-rw-r--r-- | src/dotty/tools/dotc/core/TypeApplications.scala | 753 | ||||
-rw-r--r-- | src/dotty/tools/dotc/core/TypeComparer.scala | 201 | ||||
-rw-r--r-- | src/dotty/tools/dotc/core/TypeOps.scala | 44 | ||||
-rw-r--r-- | src/dotty/tools/dotc/core/Types.scala | 147 | ||||
-rw-r--r-- | src/dotty/tools/dotc/core/tasty/TreePickler.scala | 30 | ||||
-rw-r--r-- | src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala | 53 |
14 files changed, 724 insertions, 588 deletions
diff --git a/src/dotty/tools/dotc/core/ConstraintHandling.scala b/src/dotty/tools/dotc/core/ConstraintHandling.scala index 577b958c8..ff7afe99a 100644 --- a/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -232,6 +232,16 @@ trait ConstraintHandling { } } + /** Instantiate `param` to `tp` if the constraint stays satisfiable */ + protected def tryInstantiate(param: PolyParam, tp: Type): Boolean = { + val saved = constraint + constraint = + if (addConstraint(param, tp, fromBelow = true) && + addConstraint(param, tp, fromBelow = false)) constraint.replace(param, tp) + else saved + constraint ne saved + } + /** Check that constraint is fully propagated. See comment in Config.checkConstraintsPropagated */ def checkPropagated(msg: => String)(result: Boolean): Boolean = { if (Config.checkConstraintsPropagated && result && addConstraintInvocations == 0) { diff --git a/src/dotty/tools/dotc/core/Definitions.scala b/src/dotty/tools/dotc/core/Definitions.scala index 4556dd9d5..5f794f2d5 100644 --- a/src/dotty/tools/dotc/core/Definitions.scala +++ b/src/dotty/tools/dotc/core/Definitions.scala @@ -588,6 +588,12 @@ class Definitions { name.startsWith(prefix) && name.drop(prefix.length).forall(_.isDigit) } + def isBottomClass(cls: Symbol) = cls == NothingClass || cls == NullClass + def isBottomType(tp: Type) = tp match { + case tp: TypeRef => isBottomClass(tp.symbol) + case _ => false + } + def isFunctionClass(cls: Symbol) = isVarArityClass(cls, tpnme.Function) def isAbstractFunctionClass(cls: Symbol) = isVarArityClass(cls, tpnme.AbstractFunction) def isTupleClass(cls: Symbol) = isVarArityClass(cls, tpnme.Tuple) @@ -642,9 +648,9 @@ class Definitions { * to be the type parameters of a higher-kided type). This is a class symbol that * would be generated by the following schema. * - * class LambdaXYZ extends Object with P1 with ... with Pn { - * type v_1 $hk$Arg0; ...; type v_N $hk$ArgN; - * type Apply + * trait LambdaXYZ extends Object with P1 with ... with Pn { + * type v_1 hk$0; ...; type v_N hk$N; + * type +$Apply * } * * Here: @@ -669,7 +675,7 @@ class Definitions { val cls = denot.asClass.classSymbol val paramDecls = newScope for (i <- 0 until vcs.length) - newTypeParam(cls, tpnme.LambdaArgName(i), varianceFlags(vcs(i)), paramDecls) + newTypeParam(cls, tpnme.hkArg(i), varianceFlags(vcs(i)), paramDecls) newTypeField(cls, tpnme.hkApply, Covariant, paramDecls) val parentTraitRefs = for (i <- 0 until vcs.length if vcs(i) != 0) @@ -679,7 +685,7 @@ class Definitions { } } - val traitName = tpnme.LambdaTraitName(vcs) + val traitName = tpnme.hkLambda(vcs) def createTrait = { val cls = newClassSymbol( diff --git a/src/dotty/tools/dotc/core/Denotations.scala b/src/dotty/tools/dotc/core/Denotations.scala index b6519e1cd..fcd60ef16 100644 --- a/src/dotty/tools/dotc/core/Denotations.scala +++ b/src/dotty/tools/dotc/core/Denotations.scala @@ -324,7 +324,7 @@ object Denotations { info1 // follow Scala2 linearization - // compare with way merge is performed in SymDenotation#computeMembersNamed else - throw new MergeError(s"${ex.getMessage} as members of type ${pre.show}") + throw new MergeError(s"${ex.getMessage} as members of type ${pre.show}", ex.tp1, ex.tp2) } new JointRefDenotation(sym, jointInfo, denot1.validFor & denot2.validFor) } diff --git a/src/dotty/tools/dotc/core/Flags.scala b/src/dotty/tools/dotc/core/Flags.scala index c8a9dc13b..42d06c2ab 100644 --- a/src/dotty/tools/dotc/core/Flags.scala +++ b/src/dotty/tools/dotc/core/Flags.scala @@ -306,10 +306,12 @@ object Flags { /** A case parameter accessor */ final val CaseAccessor = termFlag(25, "<caseaccessor>") - /** An type parameter which is an alias for some other (non-visible) type parameter */ - final val TypeArgument = typeFlag(25, "<type-param-inst>") + /** A binding for a type parameter of a base class or trait. + * TODO: Replace with combination of isType, ExpandedName, and Override? + */ + final val BaseTypeArg = typeFlag(25, "<basetypearg>") - final val CaseAccessorOrTypeArgument = CaseAccessor.toCommonFlags + final val CaseAccessorOrBaseTypeArg = CaseAccessor.toCommonFlags /** A super accessor */ final val SuperAccessor = termFlag(26, "<superaccessor>") @@ -446,7 +448,7 @@ object Flags { final val FromStartFlags = AccessFlags | Module | Package | Deferred | Final | MethodOrHKCommon | Param | ParamAccessor | Scala2ExistentialCommon | InSuperCall | Touched | JavaStatic | CovariantOrOuter | ContravariantOrLabel | ExpandedName | AccessorOrSealed | - CaseAccessorOrTypeArgument | Fresh | Frozen | Erroneous | ImplicitCommon | Permanent | + CaseAccessorOrBaseTypeArg | Fresh | Frozen | Erroneous | ImplicitCommon | Permanent | LazyOrTrait | SuperAccessorOrScala2x | SelfNameOrImplClass assert(FromStartFlags.isTermFlags && FromStartFlags.isTypeFlags) @@ -545,7 +547,7 @@ object Flags { final val TypeParamOrAccessor = TypeParam | TypeParamAccessor /** If symbol of a type alias has these flags, prefer the alias */ - final val AliasPreferred = TypeParam | TypeArgument | ExpandedName + final val AliasPreferred = TypeParam | BaseTypeArg | ExpandedName /** A covariant type parameter instance */ final val LocalCovariant = allOf(Local, Covariant) @@ -583,6 +585,9 @@ object Flags { /** A private[this] parameter accessor */ final val PrivateLocalParamAccessor = allOf(Private, Local, ParamAccessor) + /** A parameter forwarder */ + final val ParamForwarder = allOf(Method, Stable, ParamAccessor) + /** A private[this] parameter */ final val PrivateLocalParam = allOf(Private, Local, Param) diff --git a/src/dotty/tools/dotc/core/NameOps.scala b/src/dotty/tools/dotc/core/NameOps.scala index 1a2646347..6c1930c9f 100644 --- a/src/dotty/tools/dotc/core/NameOps.scala +++ b/src/dotty/tools/dotc/core/NameOps.scala @@ -81,6 +81,7 @@ object NameOps { def isScala2LocalSuffix = name.endsWith(" ") def isModuleVarName(name: Name): Boolean = name.stripAnonNumberSuffix endsWith MODULE_VAR_SUFFIX + def isSelectorName = name.startsWith(" ") && name.tail.forall(_.isDigit) /** Is name a variable name? */ def isVariableName: Boolean = name.length > 0 && { @@ -99,19 +100,22 @@ object NameOps { } /** Is this the name of a higher-kinded type parameter of a Lambda? */ - def isLambdaArgName = + def isHkArgName = name.length > 0 && - name.head == tpnme.LAMBDA_ARG_PREFIXhead && - name.startsWith(tpnme.LAMBDA_ARG_PREFIX) && { - val digits = name.drop(tpnme.LAMBDA_ARG_PREFIX.length) + name.head == tpnme.hkArgPrefixHead && + name.startsWith(tpnme.hkArgPrefix) && { + val digits = name.drop(tpnme.hkArgPrefixLength) digits.length <= 4 && digits.forall(_.isDigit) } /** The index of the higher-kinded type parameter with this name. * Pre: isLambdaArgName. */ - def LambdaArgIndex: Int = - name.drop(tpnme.LAMBDA_ARG_PREFIX.length).toString.toInt + def hkArgIndex: Int = + name.drop(tpnme.hkArgPrefixLength).toString.toInt + + def isLambdaTraitName(implicit ctx: Context): Boolean = + name.startsWith(tpnme.hkLambdaPrefix) /** If the name ends with $nn where nn are * all digits, strip the $ and the digits. diff --git a/src/dotty/tools/dotc/core/StdNames.scala b/src/dotty/tools/dotc/core/StdNames.scala index e8cddd3d4..e2add1a52 100644 --- a/src/dotty/tools/dotc/core/StdNames.scala +++ b/src/dotty/tools/dotc/core/StdNames.scala @@ -173,8 +173,6 @@ object StdNames { final val WILDCARD_STAR: N = "_*" final val REIFY_TREECREATOR_PREFIX: N = "$treecreator" final val REIFY_TYPECREATOR_PREFIX: N = "$typecreator" - final val LAMBDA_ARG_PREFIX: N = "hk$" - final val LAMBDA_ARG_PREFIXhead: Char = LAMBDA_ARG_PREFIX.head final val AbstractFunction: N = "AbstractFunction" final val Any: N = "Any" @@ -314,7 +312,6 @@ object StdNames { val AnnotatedType: N = "AnnotatedType" val AppliedTypeTree: N = "AppliedTypeTree" - val hkApply: N = "$apply" val ArrayAnnotArg: N = "ArrayAnnotArg" val Constant: N = "Constant" val ConstantType: N = "ConstantType" @@ -322,7 +319,6 @@ object StdNames { val Flag : N = "Flag" val Ident: N = "Ident" val Import: N = "Import" - val LambdaPrefix: N = "Lambda$" val Literal: N = "Literal" val LiteralAnnotArg: N = "LiteralAnnotArg" val Modifiers: N = "Modifiers" @@ -530,9 +526,14 @@ object StdNames { val nothingRuntimeClass: N = "scala.runtime.Nothing$" val nullRuntimeClass: N = "scala.runtime.Null$" - val synthSwitch: N = "$synthSwitch" + val hkApply: N = "$Apply" + val hkArgPrefix: N = "$hk" + val hkLambdaPrefix: N = "Lambda$" + val hkArgPrefixHead: Char = hkArgPrefix.head + val hkArgPrefixLength: Int = hkArgPrefix.length + // unencoded operators object raw { final val AMP : N = "&" @@ -742,14 +743,13 @@ object StdNames { def syntheticTypeParamNames(num: Int): List[TypeName] = (0 until num).map(syntheticTypeParamName)(breakOut) - def LambdaTraitName(vcs: List[Int]): TypeName = LambdaPrefix ++ vcs.map(varianceSuffix).mkString - def LambdaArgName(n: Int) = LAMBDA_ARG_PREFIX ++ n.toString - - final val Conforms = encode("<:<") + def hkLambda(vcs: List[Int]): TypeName = hkLambdaPrefix ++ vcs.map(varianceSuffix).mkString + def hkArg(n: Int): TypeName = hkArgPrefix ++ n.toString def varianceSuffix(v: Int): Char = varianceSuffixes.charAt(v + 1) - val varianceSuffixes = "NIP" + + final val Conforms = encode("<:<") } abstract class JavaNames[N <: Name] extends DefinedNames[N] { diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index d7fa183c9..e351557a3 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -167,7 +167,6 @@ object SymDenotations { completions.println(i"${" " * indent}completing ${if (isType) "type" else "val"} $name") indent += 1 } - indent += 1 if (myFlags is Touched) throw CyclicReference(this) myFlags |= Touched @@ -446,7 +445,7 @@ object SymDenotations { /** is this symbol a trait representing a type lambda? */ final def isLambdaTrait(implicit ctx: Context): Boolean = - isClass && name.startsWith(tpnme.LambdaPrefix) && owner == defn.ScalaPackageClass + isClass && name.startsWith(tpnme.hkLambdaPrefix) && owner == defn.ScalaPackageClass /** Is this symbol a package object or its module class? */ def isPackageObject(implicit ctx: Context): Boolean = { diff --git a/src/dotty/tools/dotc/core/Symbols.scala b/src/dotty/tools/dotc/core/Symbols.scala index e33f9651e..2a76f18d8 100644 --- a/src/dotty/tools/dotc/core/Symbols.scala +++ b/src/dotty/tools/dotc/core/Symbols.scala @@ -368,7 +368,7 @@ object Symbols { type ThisName <: Name - //assert(_id != 30214) + //assert(id != 4285) /** The last denotation of this symbol */ private[this] var lastDenot: SymDenotation = _ diff --git a/src/dotty/tools/dotc/core/TypeApplications.scala b/src/dotty/tools/dotc/core/TypeApplications.scala index 661975dab..fbab4ee39 100644 --- a/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/src/dotty/tools/dotc/core/TypeApplications.scala @@ -36,6 +36,145 @@ object TypeApplications { case tp: TypeBounds => tp.hi case _ => tp } + + /** Does the variance of `sym1` conform to the variance of `sym2`? + * This is the case if the variances are the same or `sym` is nonvariant. + */ + def varianceConforms(sym1: TypeSymbol, sym2: TypeSymbol)(implicit ctx: Context) = + sym1.variance == sym2.variance || sym2.variance == 0 + + def variancesConform(syms1: List[TypeSymbol], syms2: List[TypeSymbol])(implicit ctx: Context) = + syms1.corresponds(syms2)(varianceConforms) + + /** Extractor for + * + * [v1 X1: B1, ..., vn Xn: Bn] -> T + * ==> + * Lambda$_v1...vn { type $hk_i: B_i, type $Apply = [X_i := this.$Arg_i] T } + */ + object TypeLambda { + def apply(variances: List[Int], + argBoundsFns: List[RefinedType => TypeBounds], + bodyFn: RefinedType => Type)(implicit ctx: Context): Type = { + def argRefinements(parent: Type, i: Int, bs: List[RefinedType => TypeBounds]): Type = bs match { + case b :: bs1 => + argRefinements(RefinedType(parent, tpnme.hkArg(i), b), i + 1, bs1) + case nil => + parent + } + assert(variances.nonEmpty) + assert(argBoundsFns.length == variances.length) + RefinedType( + argRefinements(defn.LambdaTrait(variances).typeRef, 0, argBoundsFns), + tpnme.hkApply, bodyFn(_).bounds.withVariance(1)) + } + + def unapply(tp: Type)(implicit ctx: Context): Option[(List[Int], List[TypeBounds], Type)] = tp match { + case app @ RefinedType(parent, tpnme.hkApply) => + val cls = parent.typeSymbol + val variances = cls.typeParams.map(_.variance) + def collectBounds(t: Type, acc: List[TypeBounds]): List[TypeBounds] = t match { + case t @ RefinedType(p, rname) => + assert(rname.isHkArgName) + collectBounds(p, t.refinedInfo.bounds :: acc) + case TypeRef(_, lname) => + assert(lname.isLambdaTraitName) + acc + } + val argBounds = collectBounds(parent, Nil) + Some((variances, argBounds, app.refinedInfo.argInfo)) + case _ => + None + } + } + + /** Extractor for + * + * [v1 X1: B1, ..., vn Xn: Bn] -> C[X1, ..., Xn] + * + * where v1, ..., vn and B1, ..., Bn are the variances and bounds of the type parameters + * of the class C. + * + * @param tycon C + */ + object EtaExpansion { + def apply(tycon: TypeRef)(implicit ctx: Context) = { + assert(tycon.isEtaExpandable) + tycon.EtaExpand(tycon.typeParams) + } + + def unapply(tp: Type)(implicit ctx: Context): Option[TypeRef] = { + def argsAreForwarders(args: List[Type], n: Int): Boolean = args match { + case Nil => + n == 0 + case TypeRef(RefinedThis(rt), sel) :: args1 => + rt.eq(tp) && sel == tpnme.hkArg(n - 1) && argsAreForwarders(args1, n - 1) + case _ => + false + } + tp match { + case TypeLambda(_, argBounds, AppliedType(fn: TypeRef, args)) + if argsAreForwarders(args, tp.typeParams.length) => Some(fn) + case _ => None + } + } + } + + /** Extractor for type application T[U_1, ..., U_n]. This is the refined type + * + * T { type p_1 v_1= U_1; ...; type p_n v_n= U_n } + * + * where v_i, p_i are the variances and names of the type parameters of T, + * If `T`'s class symbol is a lambda trait, follow the refined type with a + * projection + * + * T { ... } # $Apply + */ + object AppliedType { + def apply(tp: Type, args: List[Type])(implicit ctx: Context): Type = tp.appliedTo(args) + + def unapply(tp: Type)(implicit ctx: Context): Option[(Type, List[Type])] = tp match { + case TypeRef(prefix, tpnme.hkApply) => unapp(prefix) + case _ => + unapp(tp) match { + case Some((tycon: TypeRef, _)) if tycon.symbol.isLambdaTrait => + // We are seeing part of a lambda abstraction, not an applied type + None + case x => x + } + } + + private def unapp(tp: Type)(implicit ctx: Context): Option[(Type, List[Type])] = tp match { + case _: RefinedType => + val tparams = tp.classSymbol.typeParams + if (tparams.isEmpty) None + else { + val argBuf = new mutable.ListBuffer[Type] + def stripArgs(tp: Type, n: Int): Type = + if (n == 0) tp + else tp match { + case tp @ RefinedType(parent, pname) if pname == tparams(n - 1).name => + val res = stripArgs(parent, n - 1) + if (res.exists) argBuf += tp.refinedInfo.argInfo + res + case _ => + NoType + } + val res = stripArgs(tp, tparams.length) + if (res.exists) Some((res, argBuf.toList)) else None + } + case _ => None + } + } + + /** Adapt all arguments to possible higher-kinded type parameters using etaExpandIfHK + */ + def etaExpandIfHK(tparams: List[Symbol], args: List[Type])(implicit ctx: Context): List[Type] = + if (tparams.isEmpty) args + else args.zipWithConserve(tparams)((arg, tparam) => arg.etaExpandIfHK(tparam.infoOrCompleter)) + + def argRefs(rt: RefinedType, n: Int)(implicit ctx: Context) = + List.range(0, n).map(i => RefinedThis(rt).select(tpnme.hkArg(i))) } import TypeApplications._ @@ -61,27 +200,27 @@ class TypeApplications(val self: Type) extends AnyVal { case self: ClassInfo => self.cls.typeParams case self: TypeRef => - val tsym = self.typeSymbol + val tsym = self.symbol if (tsym.isClass) tsym.typeParams else if (tsym.isAliasType) self.underlying.typeParams - else { - val lam = LambdaClass(forcing = false) - if (lam.exists) lam.typeParams else Nil - } + else if (tsym.isCompleting) + // We are facing a problem when computing the type parameters of an uncompleted + // abstract type. We can't access the bounds of the symbol yet because that + // would cause a cause a cyclic reference. So we return `Nil` instead + // and try to make up for it later. The acrobatics in Scala2Unpicker#readType + // for reading a TypeRef show what's neeed. + Nil + else tsym.info.typeParams case self: RefinedType => - def isBoundedLambda(tp: Type): Boolean = tp match { - case tp: TypeRef => tp.typeSymbol.isLambdaTrait - case tp: RefinedType => tp.refinedName.isLambdaArgName && isBoundedLambda(tp.parent) - case _ => false - } - val tparams = self.parent.typeParams - self.refinedInfo match { - case rinfo: TypeBounds if rinfo.isAlias || isBoundedLambda(self) => - tparams.filterNot(_.name == self.refinedName) - case _ => - tparams + // inlined and optimized version of + // val sym = self.LambdaTrait + // if (sym.exists) return sym.typeParams + if (self.refinedName == tpnme.hkApply) { + val sym = self.parent.classSymbol + if (sym.isLambdaTrait) return sym.typeParams } - case self: SingletonType => + self.parent.typeParams.filterNot(_.name == self.refinedName) + case self: SingletonType => Nil case self: TypeProxy => self.underlying.typeParams @@ -90,76 +229,24 @@ class TypeApplications(val self: Type) extends AnyVal { } } - /** The type parameters of the underlying class. - * This is like `typeParams`, except for 3 differences. - * First, it does not adjust type parameters in refined types. I.e. type arguments - * do not remove corresponding type parameters. - * Second, it will return Nil for BoundTypes because we might get a NullPointer exception - * on PolyParam#underlying otherwise (demonstrated by showClass test). - * Third, it won't return abstract higher-kinded type parameters, i.e. the type parameters of - * an abstract type are always empty. - */ - final def rawTypeParams(implicit ctx: Context): List[TypeSymbol] = { - self match { - case self: ClassInfo => - self.cls.typeParams - case self: TypeRef => - val tsym = self.typeSymbol - if (tsym.isClass) tsym.typeParams - else if (tsym.isAliasType) self.underlying.rawTypeParams - else Nil - case _: BoundType | _: SingletonType => - Nil - case self: TypeProxy => - self.underlying.rawTypeParams - case _ => - Nil - } + /** The Lambda trait underlying a type lambda */ + def LambdaTrait(implicit ctx: Context): Symbol = self.stripTypeVar match { + case RefinedType(parent, tpnme.hkApply) => + val sym = self.classSymbol + if (sym.isLambdaTrait) sym else NoSymbol + case TypeBounds(lo, hi) => hi.LambdaTrait + case _ => NoSymbol } - /** If type `self` is equal, aliased-to, or upperbounded-by a type of the form - * `LambdaXYZ { ... }`, the class symbol of that type, otherwise NoSymbol. - * symbol of that type, otherwise NoSymbol. - * @param forcing if set, might force completion. If not, never forces - * but returns NoSymbol when it would have to otherwise. - */ - def LambdaClass(forcing: Boolean)(implicit ctx: Context): Symbol = track("LambdaClass") { self.stripTypeVar match { - case self: TypeRef => - val sym = self.symbol - if (sym.isLambdaTrait) sym - else if (sym.isClass || sym.isCompleting && !forcing) NoSymbol - else self.info.LambdaClass(forcing) - case self: TypeProxy => - self.underlying.LambdaClass(forcing) - case _ => - NoSymbol - }} - - /** Is type `self` equal, aliased-to, or upperbounded-by a type of the form - * `LambdaXYZ { ... }`? - */ - def isLambda(implicit ctx: Context): Boolean = - LambdaClass(forcing = true).exists - - /** Same is `isLambda`, except that symbol denotations are not forced - * Symbols in completion count as not lambdas. - */ - def isSafeLambda(implicit ctx: Context): Boolean = - LambdaClass(forcing = false).exists - - /** Is type `self` a Lambda with all hk$i fields fully instantiated? */ - def isInstantiatedLambda(implicit ctx: Context): Boolean = - isSafeLambda && typeParams.isEmpty - /** Is receiver type higher-kinded (i.e. of kind != "*")? */ def isHK(implicit ctx: Context): Boolean = self.dealias match { case self: TypeRef => self.info.isHK - case RefinedType(_, name) => name == tpnme.hkApply || name.isLambdaArgName + case RefinedType(_, name) => name == tpnme.hkApply case TypeBounds(_, hi) => hi.isHK case _ => false } - /** is receiver of the form T#$apply? */ + /** is receiver of the form T#$Apply? */ def isHKApply: Boolean = self match { case TypeRef(_, name) => name == tpnme.hkApply case _ => false @@ -173,101 +260,237 @@ class TypeApplications(val self: Type) extends AnyVal { * * but without forcing anything. */ - def noHK(implicit ctx: Context): Boolean = self.stripTypeVar match { + def classNotLambda(implicit ctx: Context): Boolean = self.stripTypeVar match { case self: RefinedType => - self.parent.noHK + self.parent.classNotLambda case self: TypeRef => - (self.denot.exists) && { + self.denot.exists && { val sym = self.symbol if (sym.isClass) !sym.isLambdaTrait - else sym.isCompleted && self.info.isAlias && self.info.bounds.hi.noHK + else sym.isCompleted && self.info.isAlias && self.info.bounds.hi.classNotLambda } case _ => false } - /** Encode the type resulting from applying this type to given arguments */ - final def appliedTo(args: List[Type])(implicit ctx: Context): Type = /*>|>*/ track("appliedTo") /*<|<*/ { - def matchParams(tp: Type, tparams: List[TypeSymbol], args: List[Type]): Type = args match { - case arg :: args1 => - if (tparams.isEmpty) { - println(s"applied type mismatch: $self $args, typeParams = $typeParams, tsym = ${self.typeSymbol.debugString}") // !!! DEBUG - println(s"precomplete decls = ${self.typeSymbol.unforcedDecls.toList.map(_.denot).mkString("\n ")}") - } - val tparam = tparams.head - val tp1 = RefinedType(tp, tparam.name, arg.toBounds(tparam)) - matchParams(tp1, tparams.tail, args1) - case nil => tp - } + /** Lambda abstract `self` with given type parameters. Examples: + * + * type T[X] = U becomes type T = [X] -> U + * type T[X] >: L <: U becomes type T >: L <: ([X] -> _ <: U) + */ + def LambdaAbstract(tparams: List[Symbol])(implicit ctx: Context): Type = { - /** Instantiate type `tp` with `args`. - * @param original The original type for which we compute the type parameters - * This makes a difference for refinement types, because - * refinements bind type parameters and thereby remove them - * from `typeParams`. + /** Replace references to type parameters with references to hk arguments `this.$hk_i` + * Care is needed not to cause cycles, hence `SafeSubstMap`. */ - def instantiate(tp: Type, original: Type): Type = tp match { - case tp: TypeRef => - val tsym = tp.symbol - if (tsym.isAliasType) tp.underlying.appliedTo(args) - else { - val safeTypeParams = - if (tsym.isClass || !tp.typeSymbol.isCompleting) original.typeParams - else { - ctx.warning(i"encountered F-bounded higher-kinded type parameters for $tsym; assuming they are invariant") - defn.LambdaTrait(args map alwaysZero).typeParams // @@@ can we force? - } - matchParams(tp, safeTypeParams, args) - } - case tp: RefinedType => - val redux = tp.EtaReduce - if (redux.exists) redux.appliedTo(args) // Rewrite ([hk$0] => C[hk$0])(T) to C[T] - else tp.derivedRefinedType( - instantiate(tp.parent, original), - tp.refinedName, - tp.refinedInfo) - case tp: TypeProxy => - instantiate(tp.underlying, original) - case tp: PolyType => - tp.instantiate(args) - case ErrorType => - tp + def internalize[T <: Type](tp: T) = + (rt: RefinedType) => + new ctx.SafeSubstMap(tparams, argRefs(rt, tparams.length)) + .apply(tp).asInstanceOf[T] + + def expand(tp: Type) = { + TypeLambda( + tparams.map(_.variance), + tparams.map(tparam => internalize(self.memberInfo(tparam).bounds)), + internalize(tp)) } + self match { + case self: TypeAlias => + self.derivedTypeAlias(expand(self.alias)) + case self @ TypeBounds(lo, hi) => + self.derivedTypeBounds(lo, expand(TypeBounds.upper(hi))) + case _ => expand(self) + } + } - /** Same as isHK, except we classify all abstract types as HK, - * (they must be, because they are applied). This avoids some forcing and - * CyclicReference errors of the standard isHK. - */ - def isKnownHK(tp: Type): Boolean = tp match { - case tp: TypeRef => - val sym = tp.symbol - if (sym.isClass) sym.isLambdaTrait - else !sym.isAliasType || isKnownHK(tp.info) - case tp: TypeProxy => isKnownHK(tp.underlying) - case _ => false + /** A type ref is eta expandable if it refers to a non-lambda class. + * In that case we can look for parameterized base types of the type + * to eta expand them. + */ + def isEtaExpandable(implicit ctx: Context) = self match { + case self: TypeRef => self.symbol.isClass && !self.name.isLambdaTraitName + case _ => false + } + + /** Convert a type constructor `TC` which has type parameters `T1, ..., Tn` + * in a context where type parameters `U1,...,Un` are expected to + * + * LambdaXYZ { Apply = TC[hk$0, ..., hk$n] } + * + * Here, XYZ corresponds to the variances of + * - `U1,...,Un` if the variances of `T1,...,Tn` are pairwise compatible with `U1,...,Un`, + * - `T1,...,Tn` otherwise. + * v1 is compatible with v2, if v1 = v2 or v2 is non-variant. + */ + def EtaExpand(tparams: List[TypeSymbol])(implicit ctx: Context): Type = { + val tparamsToUse = if (variancesConform(typeParams, tparams)) tparams else typeParams + self.appliedTo(tparams map (_.typeRef)).LambdaAbstract(tparamsToUse) + //.ensuring(res => res.EtaReduce =:= self, s"res = $res, core = ${res.EtaReduce}, self = $self, hc = ${res.hashCode}") + } + + /** Eta expand the prefix in front of any refinements. + * @param tparamsForBottom Type parameters to use if core is a bottom type + */ + def EtaExpandCore(tparamsForBottom: List[TypeSymbol])(implicit ctx: Context): Type = self.stripTypeVar match { + case self: RefinedType => + self.derivedRefinedType(self.parent.EtaExpandCore(tparamsForBottom), self.refinedName, self.refinedInfo) + case tp: TypeRef if defn.isBottomClass(tp.symbol) => + self.LambdaAbstract(tparamsForBottom) + case _ => + self.EtaExpand(self.typeParams) + } + + /** Eta expand if `self` is a (non-lambda) class reference and `bound` is a higher-kinded type */ + def etaExpandIfHK(bound: Type)(implicit ctx: Context): Type = { + val boundLambda = bound.LambdaTrait + val hkParams = boundLambda.typeParams + if (hkParams.isEmpty) self + else self match { + case self: TypeRef if self.symbol.isClass && self.typeParams.length == hkParams.length => + EtaExpansion(self) + case _ => self } + } - if (args.isEmpty || ctx.erasedTypes) self + /** 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 boundLambda = bound.LambdaTrait + val hkParams = boundLambda.typeParams + if (hkParams.isEmpty) self else { - val res = instantiate(self, self) - if (isKnownHK(res)) TypeRef(res, tpnme.hkApply) else res + def adaptArg(arg: Type): Type = arg match { + case arg: TypeRef if arg.symbol.isLambdaTrait && + !arg.symbol.typeParams.corresponds(hkParams)(_.variance == _.variance) && + arg.symbol.typeParams.corresponds(hkParams)(varianceConforms) => + arg.prefix.select(boundLambda) + case arg: RefinedType => + arg.derivedRefinedType(adaptArg(arg.parent), arg.refinedName, arg.refinedInfo) + case arg @ TypeAlias(alias) => + arg.derivedTypeAlias(adaptArg(alias)) + case arg @ TypeBounds(lo, hi) => + arg.derivedTypeBounds(lo, adaptArg(hi)) + case _ => + arg + } + adaptArg(self) } } - /** Simplify a fully instantiated type of the form `LambdaX{... type Apply = T } # Apply` to `T`. + /** Encode + * + * T[U1, ..., Un] + * + * where + * @param self = `T` + * @param args = `U1,...,Un` + * performing the following simplifications + * + * 1. If `T` is an eta expansion `[X1,..,Xn] -> C[X1,...,Xn]` of class `C` compute + * `C[U1, ..., Un]` instead. + * 2. If `T` is some other type lambda `[X1,...,Xn] -> S` none of the arguments + * `U1,...,Un` is a wildcard, compute `[X1:=U1, ..., Xn:=Un]S` instead. + * 3. If `T` is a polytype, instantiate it to `U1,...,Un`. */ - def simplifyApply(implicit ctx: Context): Type = self match { - case self @ TypeRef(prefix, tpnme.hkApply) if prefix.isInstantiatedLambda => - prefix.member(tpnme.hkApply).info match { - case TypeAlias(alias) => alias - case _ => self + final def appliedTo(args: List[Type])(implicit ctx: Context): Type = /*>|>*/ track("appliedTo") /*<|<*/ { + def substHkArgs = new TypeMap { + def apply(tp: Type): Type = tp match { + case TypeRef(RefinedThis(rt), name) if rt.eq(self) && name.isHkArgName => + args(name.hkArgIndex) + case _ => + mapOver(tp) } - case _ => self + } + if (args.isEmpty || ctx.erasedTypes) self + else self.stripTypeVar match { + case EtaExpansion(self1) => + self1.appliedTo(args) + case TypeLambda(_, _, body) if !args.exists(_.isInstanceOf[TypeBounds]) => + substHkArgs(body) + case self: PolyType => + self.instantiate(args) + case _ => + appliedTo(args, typeParams) + } + } + + /** Encode application `T[U1, ..., Un]` without simplifications, where + * @param self = `T` + * @param args = `U1, ..., Un` + * @param tparams are assumed to be the type parameters of `T`. + */ + final def appliedTo(args: List[Type], typParams: List[TypeSymbol])(implicit ctx: Context): Type = { + def matchParams(t: Type, tparams: List[TypeSymbol], args: List[Type])(implicit ctx: Context): Type = args match { + case arg :: args1 => + try { + val tparam :: tparams1 = tparams + matchParams(RefinedType(t, tparam.name, arg.toBounds(tparam)), tparams1, args1) + } catch { + case ex: MatchError => + println(s"applied type mismatch: $self $args, typeParams = $typParams") // !!! DEBUG + //println(s"precomplete decls = ${self.typeSymbol.unforcedDecls.toList.map(_.denot).mkString("\n ")}") + throw ex + } + case nil => t + } + assert(args.nonEmpty) + matchParams(self, typParams, args) match { + case refined @ RefinedType(_, pname) if pname.isHkArgName => + TypeRef(refined, tpnme.hkApply) + case refined => + refined + } } final def appliedTo(arg: Type)(implicit ctx: Context): Type = appliedTo(arg :: Nil) final def appliedTo(arg1: Type, arg2: Type)(implicit ctx: Context): Type = appliedTo(arg1 :: arg2 :: Nil) + /** A cycle-safe version of `appliedTo` where computing type parameters do not force + * the typeconstructor. Instead, if the type constructor is completing, we make + * up hk type parameters matching the arguments. This is needed when unpickling + * Scala2 files such as `scala.collection.generic.Mapfactory`. + */ + final def safeAppliedTo(args: List[Type])(implicit ctx: Context) = { + val safeTypeParams = self match { + case self: TypeRef if !self.symbol.isClass && self.symbol.isCompleting => + // This happens when unpickling e.g. scala$collection$generic$GenMapFactory$$CC + ctx.warning(i"encountered F-bounded higher-kinded type parameters for ${self.symbol}; assuming they are invariant") + defn.LambdaTrait(args map alwaysZero).typeParams + case _ => + typeParams + } + appliedTo(args, safeTypeParams) + } + /** Turn this type, which is used as an argument for * type parameter `tparam`, into a TypeBounds RHS */ @@ -281,7 +504,7 @@ class TypeApplications(val self: Type) extends AnyVal { else TypeAlias(self, v) } - /** The type arguments of this type's base type instance wrt.`base`. + /** The type arguments of this type's base type instance wrt. `base`. * Existential types in arguments are returned as TypeBounds instances. */ final def baseArgInfos(base: Symbol)(implicit ctx: Context): List[Type] = @@ -370,26 +593,9 @@ class TypeApplications(val self: Type) extends AnyVal { * otherwise return Nil. * Existential types in arguments are returned as TypeBounds instances. */ - final def argInfos(implicit ctx: Context): List[Type] = { - var tparams: List[TypeSymbol] = null - def recur(tp: Type, refineCount: Int): mutable.ListBuffer[Type] = tp.stripTypeVar match { - case tp @ RefinedType(tycon, name) => - val buf = recur(tycon, refineCount + 1) - if (buf == null) null - else { - if (tparams == null) tparams = tycon.typeParams - if (buf.size < tparams.length) { - val tparam = tparams(buf.size) - if (name == tparam.name) buf += tp.refinedInfo.argInfo - else null - } else null - } - case _ => - if (refineCount == 0) null - else new mutable.ListBuffer[Type] - } - val buf = recur(self, 0) - if (buf == null) Nil else buf.toList + final def argInfos(implicit ctx: Context): List[Type] = self match { + case AppliedType(tycon, args) => args + case _ => Nil } /** Argument types where existential types in arguments are disallowed */ @@ -412,6 +618,11 @@ class TypeApplications(val self: Type) extends AnyVal { self } + final def typeConstructor(implicit ctx: Context): Type = self.stripTypeVar match { + case AppliedType(tycon, _) => tycon + case self => self + } + /** If this is the image of a type argument; recover the type argument, * otherwise NoType. */ @@ -421,6 +632,12 @@ class TypeApplications(val self: Type) extends AnyVal { case _ => NoType } + /** If this is a type alias, its underlying type, otherwise the type itself */ + def dropAlias(implicit ctx: Context): Type = self match { + case TypeAlias(alias) => alias + case _ => self + } + /** The element type of a sequence or array */ def elemType(implicit ctx: Context): Type = self match { case defn.ArrayOf(elemtp) => elemtp @@ -428,6 +645,9 @@ class TypeApplications(val self: Type) extends AnyVal { case _ => firstBaseArgInfo(defn.SeqClass) } + /** Does this type contain RefinedThis type with `target` as its underling + * refinement type? + */ def containsRefinedThis(target: Type)(implicit ctx: Context): Boolean = { def recur(tp: Type): Boolean = tp.stripTypeVar match { case RefinedThis(tp) => @@ -451,201 +671,4 @@ class TypeApplications(val self: Type) extends AnyVal { } recur(self) } - - /** The typed lambda abstraction of this type `T` relative to `boundSyms`. - * This is: - * - * LambdaXYZ{ bounds }{ type Apply = toHK(T) } - * - * where - * - XYZ reflects the variances of the bound symbols, - * - `bounds` consists of type declarations `type hk$i >: toHK(L) <: toHK(U), - * one for each type parameter in `T` with non-trivial bounds L,U. - * - `toHK` is a substitution that replaces every bound symbol sym_i by - * `this.hk$i`. - * - * TypeBounds are lambda abstracting by lambda abstracting their upper bound. - * - * @param cycleParanoid If `true` don't force denotation of a TypeRef unless - * its name matches one of `boundSyms`. Needed to avoid cycles - * involving F-boundes hk-types when reading Scala2 collection classes - * with new hk-scheme. - */ - def LambdaAbstract(boundSyms: List[Symbol], cycleParanoid: Boolean = false)(implicit ctx: Context): Type = { - def expand(tp: Type): Type = { - val lambda = defn.LambdaTrait(boundSyms.map(_.variance)) - def toHK(tp: Type) = (rt: RefinedType) => { - val argRefs = boundSyms.indices.toList.map(i => - RefinedThis(rt).select(tpnme.LambdaArgName(i))) - val substituted = - if (cycleParanoid) new ctx.SafeSubstMap(boundSyms, argRefs).apply(tp) - else tp.subst(boundSyms, argRefs) - substituted.bounds.withVariance(1) - } - val boundNames = new mutable.ListBuffer[Name] - val boundss = new mutable.ListBuffer[TypeBounds] - for (sym <- boundSyms) { - val bounds = sym.info.bounds - if (!(TypeBounds.empty frozen_<:< bounds)) { - boundNames += sym.name - boundss += bounds - } - } - val lambdaWithBounds = - RefinedType.make(lambda.typeRef, boundNames.toList, boundss.toList.map(toHK)) - RefinedType(lambdaWithBounds, tpnme.hkApply, toHK(tp)) - } - self match { - case self @ TypeBounds(lo, hi) => - self.derivedTypeBounds(lo, expand(TypeBounds.upper(hi))) - case _ => - expand(self) - } - } - - /** Convert a type constructor `TC` which has type parameters `T1, ..., Tn` - * in a context where type parameters `U1,...,Un` are expected to - * - * LambdaXYZ { Apply = TC[hk$0, ..., hk$n] } - * - * Here, XYZ corresponds to the variances of - * - `U1,...,Un` if the variances of `T1,...,Tn` are pairwise compatible with `U1,...,Un`, - * - `T1,...,Tn` otherwise. - * v1 is compatible with v2, if v1 = v2 or v2 is non-variant. - */ - def EtaExpand(tparams: List[Symbol])(implicit ctx: Context): Type = { - def varianceCompatible(actual: Symbol, formal: Symbol) = - formal.variance == 0 || actual.variance == formal.variance - val tparamsToUse = - if (typeParams.corresponds(tparams)(varianceCompatible)) tparams else typeParams - self.appliedTo(tparams map (_.typeRef)).LambdaAbstract(tparamsToUse) - //.ensuring(res => res.EtaReduce =:= self, s"res = $res, core = ${res.EtaReduce}, self = $self, hc = ${res.hashCode}") - } - - /** Eta expand if `bound` is a higher-kinded type */ - def EtaExpandIfHK(bound: Type)(implicit ctx: Context): Type = - if (bound.isHK && !isHK && self.typeSymbol.isClass && typeParams.nonEmpty) EtaExpand(bound.typeParams) - else self - - /** Eta expand the prefix in front of any refinements. */ - def EtaExpandCore(implicit ctx: Context): Type = self.stripTypeVar match { - case self: RefinedType => - self.derivedRefinedType(self.parent.EtaExpandCore, self.refinedName, self.refinedInfo) - case _ => - self.EtaExpand(self.typeParams) - } - - /** If `self` is a (potentially partially instantiated) eta expansion of type T, return T, - * otherwise NoType. More precisely if `self` is of the form - * - * T { type $apply = U[T1, ..., Tn] } - * - * where - * - * - hk$0, ..., hk${m-1} are the type parameters of T - * - a sublist of the arguments Ti_k (k = 0,...,m_1) are of the form T{...}.this.hk$i_k - * - * rewrite `self` to - * - * U[T'1,...T'j] - * - * where - * - * T'j = _ >: Lj <: Uj if j is in the i_k list defined above - * where Lj and Uj are the bounds of hk$j mapped using `fromHK`. - * = fromHK(Tj) otherwise. - * - * `fromHK` is the function that replaces every occurrence of `<self>.this.hk$i` by the - * corresponding parameter reference in `U[T'1,...T'j]` - */ - def EtaReduce(implicit ctx: Context): Type = { - def etaCore(tp: Type, tparams: List[Symbol]): Type = tparams match { - case Nil => tp - case tparam :: otherParams => - tp match { - case tp: RefinedType => - tp.refinedInfo match { - case TypeAlias(TypeRef(RefinedThis(rt), rname)) - if (rname == tparam.name) && (rt eq self) => - // we have a binding T = Lambda$XYZ{...}.this.hk$i where hk$i names the current `tparam`. - val pcore = etaCore(tp.parent, otherParams) - val hkBounds = self.member(rname).info.bounds - if (TypeBounds.empty frozen_<:< hkBounds) pcore - else tp.derivedRefinedType(pcore, tp.refinedName, hkBounds) - case _ => - val pcore = etaCore(tp.parent, tparams) - if (pcore.exists) tp.derivedRefinedType(pcore, tp.refinedName, tp.refinedInfo) - else NoType - } - case _ => - NoType - } - } - // Map references `Lambda$XYZ{...}.this.hk$i to corresponding parameter references of the reduced core. - def fromHK(reduced: Type) = reduced match { - case reduced: RefinedType => - new TypeMap { - def apply(tp: Type): Type = tp match { - case TypeRef(RefinedThis(binder), name) if binder eq self => - assert(name.isLambdaArgName) - RefinedThis(reduced).select(reduced.typeParams.apply(name.LambdaArgIndex)) - case _ => - mapOver(tp) - } - }.apply(reduced) - case _ => - reduced - } - - self match { - case self @ RefinedType(parent, tpnme.hkApply) => - val lc = parent.LambdaClass(forcing = false) - self.refinedInfo match { - case TypeAlias(alias) if lc.exists => - fromHK(etaCore(alias, lc.typeParams.reverse)) - case _ => NoType - } - case _ => NoType - } - } - - /** Test whether this type has a base type of the form `B[T1, ..., Tn]` where - * the type parameters of `B` match one-by-one the variances of `tparams`, - * and where the lambda abstracted type - * - * LambdaXYZ { type Apply = B[hk$0, ..., hk${n-1}] } - * { type hk$0 = T1; ...; type hk${n-1} = Tn } - * - * satisfies predicate `p`. Try base types in the order of their occurrence in `baseClasses`. - * A type parameter matches a variance V if it has V as its variance or if V == 0. - * @param classBounds A hint to bound the search. Only types that derive from one of the - * classes in classBounds are considered. - */ - def testLifted(tparams: List[Symbol], p: Type => Boolean, classBounds: List[ClassSymbol])(implicit ctx: Context): Boolean = { - def tryLift(bcs: List[ClassSymbol]): Boolean = bcs match { - case bc :: bcs1 => - val tp = self.baseTypeWithArgs(bc) - val targs = tp.argInfos - val tycon = tp.withoutArgs(targs) - def variancesMatch(param1: Symbol, param2: Symbol) = - param2.variance == param2.variance || param2.variance == 0 - if (classBounds.exists(tycon.derivesFrom(_)) && - tycon.typeParams.corresponds(tparams)(variancesMatch)) { - val expanded = tycon.EtaExpand(tparams) - val lifted = (expanded /: targs) { (partialInst, targ) => - val tparam = partialInst.typeParams.head - RefinedType(partialInst, tparam.name, targ.bounds.withVariance(tparam.variance)) - } - ctx.traceIndented(i"eta lifting $self --> $lifted", hk) { - p(lifted) || tryLift(bcs1) - } - } - else tryLift(bcs1) - case nil => - false - } - tparams.nonEmpty && - (typeParams.hasSameLengthAs(tparams) && p(EtaExpand(tparams)) || - classBounds.nonEmpty && tryLift(self.baseClasses)) - } } diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index 1d9928e2d..163fa4919 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -12,6 +12,7 @@ import util.{Stats, DotClass, SimpleMap} import config.Config import config.Printers._ import TypeErasure.{erasedLub, erasedGlb} +import TypeApplications._ import scala.util.control.NonFatal /** Provides methods to compare types. @@ -143,27 +144,46 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { def compareNamed(tp1: Type, tp2: NamedType): Boolean = { implicit val ctx: Context = this.ctx tp2.info match { - case info2: TypeAlias => firstTry(tp1, info2.alias) + case info2: TypeAlias if tp2.prefix.isStable => + // If prefix is not stable (i.e. is not a path), then we have a true + // projection `T # A` which is treated as the existential type + // `ex(x: T)x.A`. We need to deal with the existential first before + // following the alias. If we did follow the alias we could be + // unsound as well as incomplete. An example of this was discovered in Iter2.scala. + // It failed to validate the subtype test + // + // (([+X] -> Seq[X]) & C)[SA] <: C[SA] + // + // Both sides are projections of $Apply. The left $Apply does have an + // aliased info, namely, Seq[SA]. But that is not a subtype of C[SA]. + // The problem is that, with the prefix not being a path, an aliased info + // does not necessarily give all of the information of the original projection. + // So we can't follow the alias without a backup strategy. If the alias + // would appear on the right then I believe this can be turned into a case + // of unsoundness. + isSubType(tp1, info2.alias) case _ => tp1 match { case tp1: NamedType => tp1.info match { - case info1: TypeAlias => compareNamed(info1.alias, tp2) + case info1: TypeAlias if tp1.prefix.isStable => + isSubType(info1.alias, tp2) case _ => val sym1 = tp1.symbol - (if ((sym1 ne NoSymbol) && (sym1 eq tp2.symbol)) - ctx.erasedTypes || - sym1.isStaticOwner || - isSubType(tp1.prefix, tp2.prefix) || - thirdTryNamed(tp1, tp2) - else - (tp1.name eq tp2.name) && - isSubType(tp1.prefix, tp2.prefix) && - (tp1.signature == tp2.signature) && - !tp1.isInstanceOf[WithFixedSym] && - !tp2.isInstanceOf[WithFixedSym] || - compareHK(tp1, tp2, inOrder = true) || - compareHK(tp2, tp1, inOrder = false) || - thirdTryNamed(tp1, tp2)) + if ((sym1 ne NoSymbol) && (sym1 eq tp2.symbol)) + ctx.erasedTypes || + sym1.isStaticOwner || + isSubType(tp1.prefix, tp2.prefix) || + thirdTryNamed(tp1, tp2) + else + ( (tp1.name eq tp2.name) + && isSubType(tp1.prefix, tp2.prefix) + && tp1.signature == tp2.signature + && !tp1.isInstanceOf[WithFixedSym] + && !tp2.isInstanceOf[WithFixedSym] + ) || + compareHK(tp1, tp2, inOrder = true) || + compareHK(tp2, tp1, inOrder = false) || + thirdTryNamed(tp1, tp2) } case _ => compareHK(tp2, tp1, inOrder = false) || @@ -239,9 +259,9 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { case tp1: NamedType => tp1.info match { case info1: TypeAlias => isSubType(info1.alias, tp2) - case _ => compareHK(tp1, tp2, inOrder = true) || thirdTry(tp1, tp2) - // Note: If we change the order here, doing compareHK first and following aliases second, - // we get a -Ycheck error when compiling dotc/transform. Need to investigate. + case _ => + compareHK(tp1, tp2, inOrder = true) || + thirdTry(tp1, tp2) } case tp1: PolyParam => def flagNothingBound = { @@ -336,21 +356,23 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { isSubType(tp1, tp2.parent) && (name2 == nme.WILDCARD || hasMatchingMember(name2, tp1, tp2)) } + def etaExpandedSubType(tp1: Type) = + isSubType(tp1.typeConstructor.EtaExpand(tp2.typeParams), tp2) def compareRefined: Boolean = { val tp1w = tp1.widen val skipped2 = skipMatching(tp1w, tp2) - if ((skipped2 eq tp2) || !Config.fastPathForRefinedSubtype) { - val normalPath = tp1 match { + if ((skipped2 eq tp2) || !Config.fastPathForRefinedSubtype) + tp1 match { case tp1: AndType => // Delay calling `compareRefinedSlow` because looking up a member // of an `AndType` can lead to a cascade of subtyping checks + // This twist is needed to make collection/generic/ParFactory.scala compile fourthTry(tp1, tp2) || compareRefinedSlow case _ => - compareRefinedSlow || fourthTry(tp1, tp2) + compareRefinedSlow || + fourthTry(tp1, tp2) || + needsEtaLift(tp1, tp2) && testLifted(tp1, tp2, tp2.typeParams, etaExpandedSubType) } - normalPath || - needsEtaLift(tp1, tp2) && tp1.testLifted(tp2.typeParams, isSubType(_, tp2), classBounds(tp2)) - } else // fast path, in particular for refinements resulting from parameterization. isSubType(tp1, skipped2) && isSubRefinements(tp1w.asInstanceOf[RefinedType], tp2, skipped2) @@ -412,8 +434,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { (tp2.variance > 0 && tp1.variance >= 0 || (lo2 eq NothingType) || isSubType(lo2, lo1)) && (tp2.variance < 0 && tp1.variance <= 0 || (hi2 eq AnyType) || isSubType(hi1, hi2)) case tp1: ClassInfo => - val tt = tp1.typeRef - isSubType(lo2, tt) && isSubType(tt, hi2) + tp2 contains tp1 case _ => false } @@ -471,7 +492,9 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { isNewSubType(tp1.underlying.widenExpr, tp2) || comparePaths case tp1: RefinedType => isNewSubType(tp1.parent, tp2) || - needsEtaLift(tp2, tp1) && tp2.testLifted(tp1.typeParams, isSubType(tp1, _), Nil) + needsEtaLift(tp2, tp1) && + tp2.typeParams.length == tp1.typeParams.length && + isSubType(tp1, tp2.EtaExpand(tp1.typeParams)) case AndType(tp11, tp12) => // Rewrite (T111 | T112) & T12 <: T2 to (T111 & T12) <: T2 and (T112 | T12) <: T2 // and analogously for T11 & (T121 | T122) & T12 <: T2 @@ -502,19 +525,89 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { false } - /** If `projection` is a hk projection T#$apply - * and `other` is not a hk projection, then convert `other` to a hk projection `U`, and - * continue with `T <:< U` if `inOrder` is true and `U <:< T` otherwise. + /** Does `tp` need to be eta lifted to be comparable to `target`? + * This is the case if: + * - target is a type lambda, and + * - `tp` is eta-expandable (i.e. is a non-lambda class ref) */ - def compareHK(projection: NamedType, other: Type, inOrder: Boolean) = - projection.name == tpnme.hkApply && - !other.isHKApply && - other.testLifted(projection.prefix.LambdaClass(forcing = true).typeParams, - if (inOrder) isSubType(projection.prefix, _) else isSubType(_, projection.prefix), - if (inOrder) Nil else classBounds(projection.prefix)) + private def needsEtaLift(tp: Type, target: RefinedType): Boolean = + target.refinedName == tpnme.hkApply && tp.isEtaExpandable - /** The class symbols bounding the type of the `Apply` member of `tp` */ - private def classBounds(tp: Type) = tp.member(tpnme.hkApply).info.classSymbols + /** Test whether `tp1` has a base type of the form `B[T1, ..., Tn]` where + * - `B` derives from one of the class symbols of `tp2`, + * - the type parameters of `B` match one-by-one the variances of `tparams`, + * - `B` satisfies predicate `p`. + */ + private def testLifted(tp1: Type, tp2: Type, tparams: List[TypeSymbol], p: Type => Boolean): Boolean = { + val classBounds = tp2.member(tpnme.hkApply).info.classSymbols + def recur(bcs: List[ClassSymbol]): Boolean = bcs match { + case bc :: bcs1 => + val baseRef = tp1.baseTypeRef(bc) + (classBounds.exists(bc.derivesFrom) && + variancesConform(baseRef.typeParams, tparams) && + p(baseRef.appliedTo(tp1.baseArgInfos(bc))) + || + recur(bcs1)) + case nil => + false + } + recur(tp1.baseClasses) + } + + /** If `projection` is a hk projection T#$apply with a constrainable poly param + * as type constructor and `other` is not a hk projection, then perform the following + * steps: + * + * (1) If not `inOrder` then perform the next steps until they all succeed + * for each base type of other which + * - derives from a class bound of `projection`, + * - has the same number of type parameters than `projection` + * - has type parameter variances which conform to those of `projection`. + * If `inOrder` then perform the same steps on the original `other` type. + * + * (2) Try to eta expand the constructor of `other`. + * + * (3a) In mode `TypevarsMissConetxt` replace the projection's hk constructor parameter + * by the eta expansion of step (2) reapplied to the projection's arguments. + * (3b) In normal mode, try to unify the projection's hk constructor parameter with + * the eta expansion of step(2) + * + * (4) If `inOrder`, test `projection <: other` else test `other <: projection`. + */ + def compareHK(projection: NamedType, other: Type, inOrder: Boolean): Boolean = { + def tryInfer(tp: Type): Boolean = ctx.traceIndented(i"compareHK($projection, $other, inOrder = $inOrder, constr = $tp)", subtyping) { + tp match { + case tp: TypeVar => tryInfer(tp.underlying) + case param: PolyParam if canConstrain(param) => + + def unifyWith(liftedOther: Type): Boolean = { + subtyping.println(i"unify with $liftedOther") + liftedOther.typeConstructor.widen match { + case tycon: TypeRef if tycon.isEtaExpandable && tycon.typeParams.nonEmpty => + val (ok, projection1) = + if (ctx.mode.is(Mode.TypevarsMissContext)) + (true, EtaExpansion(tycon).appliedTo(projection.argInfos)) + else + (tryInstantiate(param, EtaExpansion(tycon)), projection) + ok && + (if (inOrder) isSubType(projection1, other) else isSubType(other, projection1)) + case _ => + false + } + } + val hkTypeParams = param.typeParams + subtyping.println(i"classBounds = ${projection.prefix.member(tpnme.hkApply).info.classSymbols}") + subtyping.println(i"base classes = ${other.baseClasses}") + subtyping.println(i"type params = $hkTypeParams") + if (inOrder) unifyWith(other) + else testLifted(other, projection.prefix, hkTypeParams, unifyWith) + case _ => + false + } + } + projection.name == tpnme.hkApply && !other.isHKApply && + tryInfer(projection.prefix.typeConstructor.dealias) + } /** Returns true iff either `tp11 <:< tp21` or `tp12 <:< tp22`, trying at the same time * to keep the constraint as wide as possible. Specifically, if @@ -627,11 +720,19 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { * Further, no refinement refers back to the refined type via a refined this. * The precondition is established by `skipMatching`. */ - private def isSubRefinements(tp1: RefinedType, tp2: RefinedType, limit: Type): Boolean = - isSubType(tp1.refinedInfo, tp2.refinedInfo) && ( + private def isSubRefinements(tp1: RefinedType, tp2: RefinedType, limit: Type): Boolean = { + def hasSubRefinement(tp1: RefinedType, refine2: Type): Boolean = { + isSubType(tp1.refinedInfo, refine2) || { + // last effort: try to adapt variances of higher-kinded types if this is sound. + val adapted2 = refine2.adaptHkVariances(tp1.parent.member(tp1.refinedName).symbol.info) + adapted2.ne(refine2) && hasSubRefinement(tp1, adapted2) + } + } + hasSubRefinement(tp1, tp2.refinedInfo) && ( (tp2.parent eq limit) || isSubRefinements( tp1.parent.asInstanceOf[RefinedType], tp2.parent.asInstanceOf[RefinedType], limit)) + } /** A type has been covered previously in subtype checking if it * is some combination of TypeRefs that point to classes, where the @@ -665,14 +766,6 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { case _ => false } - /** Does `tp` need to be eta lifted to be comparable to `target`? */ - private def needsEtaLift(tp: Type, target: RefinedType): Boolean = { - // if (tp.isLambda != tp.isHK) println(i"discrepancy for $tp, isLambda = ${tp.isLambda}, isHK = ${tp.isHK}") - val name = target.refinedName - (name.isLambdaArgName || (name eq tpnme.hkApply)) && - tp.exists && !tp.isLambda // we do encounter Lambda classes without any arguments here - } - /** Narrow gadt.bounds for the type parameter referenced by `tr` to include * `bound` as an upper or lower bound (which depends on `isUpper`). * Test that the resulting bounds are still satisfiable. @@ -969,13 +1062,13 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { case tp1: TypeBounds => tp2 match { case tp2: TypeBounds => tp1 & tp2 - case tp2: ClassInfo if tp1 contains tp2.typeRef => tp2 + case tp2: ClassInfo if tp1 contains tp2 => tp2 case _ => mergeConflict(tp1, tp2) } case tp1: ClassInfo => tp2 match { case tp2: ClassInfo if tp1.cls eq tp2.cls => tp1.derivedClassInfo(tp1.prefix & tp2.prefix) - case tp2: TypeBounds if tp2 contains tp1.typeRef => tp1 + case tp2: TypeBounds if tp2 contains tp1 => tp1 case _ => mergeConflict(tp1, tp2) } case tp1 @ MethodType(names1, formals1) => @@ -1028,13 +1121,13 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { case tp1: TypeBounds => tp2 match { case tp2: TypeBounds => tp1 | tp2 - case tp2: ClassInfo if tp1 contains tp2.typeRef => tp1 + case tp2: ClassInfo if tp1 contains tp2 => tp1 case _ => mergeConflict(tp1, tp2) } case tp1: ClassInfo => tp2 match { case tp2: ClassInfo if tp1.cls eq tp2.cls => tp1.derivedClassInfo(tp1.prefix | tp2.prefix) - case tp2: TypeBounds if tp2 contains tp1.typeRef => tp2 + case tp2: TypeBounds if tp2 contains tp1 => tp2 case _ => mergeConflict(tp1, tp2) } case tp1 @ MethodType(names1, formals1) => @@ -1074,7 +1167,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { case bounds: TypeBounds => i"type bounds $bounds" case _ => tp.show } - throw new MergeError(s"cannot merge ${showType(tp1)} with ${showType(tp2)}") + throw new MergeError(s"cannot merge ${showType(tp1)} with ${showType(tp2)}", tp1, tp2) } /** Merge two lists of names. If names in corresponding positions match, keep them, diff --git a/src/dotty/tools/dotc/core/TypeOps.scala b/src/dotty/tools/dotc/core/TypeOps.scala index f3884e11a..3dfe698f0 100644 --- a/src/dotty/tools/dotc/core/TypeOps.scala +++ b/src/dotty/tools/dotc/core/TypeOps.scala @@ -61,18 +61,19 @@ trait TypeOps { this: Context => // TODO: Make standalone object. def toPrefix(pre: Type, cls: Symbol, thiscls: ClassSymbol): Type = /*>|>*/ ctx.conditionalTraceIndented(TypeOps.track, s"toPrefix($pre, $cls, $thiscls)") /*<|<*/ { if ((pre eq NoType) || (pre eq NoPrefix) || (cls is PackageClass)) tp - else if (thiscls.derivesFrom(cls) && pre.baseTypeRef(thiscls).exists) { - if (theMap != null && theMap.currentVariance <= 0 && !isLegalPrefix(pre)) - theMap.unstable = true - pre match { - case SuperType(thispre, _) => thispre - case _ => pre - } + else pre match { + case pre: SuperType => toPrefix(pre.thistpe, cls, thiscls) + case _ => + if (thiscls.derivesFrom(cls) && pre.baseTypeRef(thiscls).exists) { + if (theMap != null && theMap.currentVariance <= 0 && !isLegalPrefix(pre)) + theMap.unstable = true + pre + } + else if ((pre.termSymbol is Package) && !(thiscls is Package)) + toPrefix(pre.select(nme.PACKAGE), cls, thiscls) + else + toPrefix(pre.baseTypeRef(cls).normalizedPrefix, cls.owner, thiscls) } - else if ((pre.termSymbol is Package) && !(thiscls is Package)) - toPrefix(pre.select(nme.PACKAGE), cls, thiscls) - else - toPrefix(pre.baseTypeRef(cls).normalizedPrefix, cls.owner, thiscls) } /*>|>*/ ctx.conditionalTraceIndented(TypeOps.track, s"asSeen ${tp.show} from (${pre.show}, ${cls.show})", show = true) /*<|<*/ { // !!! DEBUG @@ -384,10 +385,9 @@ trait TypeOps { this: Context => // TODO: Make standalone object. denot.info = info } } - val typeArgFlag = if (formal is Local) TypeArgument else EmptyFlags val sym = ctx.newSymbol( cls, formal.name, - formal.flagsUNSAFE & RetainedTypeArgFlags | typeArgFlag | Override, + formal.flagsUNSAFE & RetainedTypeArgFlags | BaseTypeArg | Override, lazyInfo, coord = cls.coord) cls.enter(sym, decls) @@ -463,14 +463,20 @@ trait TypeOps { this: Context => // TODO: Make standalone object. case to @ TypeBounds(lo1, hi1) if lo1 eq hi1 => for (pref <- prefs) for (argSym <- pref.decls) - if (argSym is TypeArgument) + if (argSym is BaseTypeArg) forwardRef(argSym, from, to, cls, decls) case _ => } // println(s"normalizing $parents of $cls in ${cls.owner}") // !!! DEBUG + + // A map consolidating all refinements arising from parent type parameters var refinements: SimpleMap[TypeName, Type] = SimpleMap.Empty - var formals: SimpleMap[TypeName, Symbol] = SimpleMap.Empty + + // A map of all formal type parameters of base classes that get refined + var formals: SimpleMap[TypeName, Symbol] = SimpleMap.Empty // A map of all formal parent parameter + + // Strip all refinements from parent type, populating `refinements` and `formals` maps. def normalizeToRef(tp: Type): TypeRef = tp.dealias match { case tp: TypeRef => tp @@ -488,13 +494,17 @@ trait TypeOps { this: Context => // TODO: Make standalone object. throw new TypeError(s"unexpected parent type: $tp") } val parentRefs = parents map normalizeToRef + + // Enter all refinements into current scope. refinements foreachBinding { (name, refinedInfo) => assert(decls.lookup(name) == NoSymbol, // DEBUG s"redefinition of ${decls.lookup(name).debugString} in ${cls.showLocated}") enterArgBinding(formals(name), refinedInfo, cls, decls) } - // These two loops cannot be fused because second loop assumes that - // all arguments have been entered in `decls`. + // Forward definitions in super classes that have one of the refined paramters + // as aliases directly to the refined info. + // Note that this cannot be fused bwith the previous loop because we now + // assume that all arguments have been entered in `decls`. refinements foreachBinding { (name, refinedInfo) => forwardRefs(formals(name), refinedInfo, parentRefs) } diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 2558dcbb7..21b74e07b 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -100,7 +100,7 @@ object Types { case _ => false } - /** Is this type a (possibly aliased and/or partially applied) type reference + /** Is this type a (possibly refined or applied or aliased) type reference * to the given type symbol? * @sym The symbol to compare to. It must be a class symbol or abstract type. * It makes no sense for it to be an alias type because isRef would always @@ -113,8 +113,7 @@ object Types { case _ => this1.symbol eq sym } case this1: RefinedType => - // make sure all refinements are type arguments - this1.parent.isRef(sym) && this.argInfos.nonEmpty + this1.parent.isRef(sym) case _ => false } @@ -142,20 +141,23 @@ object Types { } /** Is this type an instance of a non-bottom subclass of the given class `cls`? */ - final def derivesFrom(cls: Symbol)(implicit ctx: Context): Boolean = this match { - case tp: TypeRef => - val sym = tp.symbol - if (sym.isClass) sym.derivesFrom(cls) else tp.underlying.derivesFrom(cls) - case tp: TypeProxy => - tp.underlying.derivesFrom(cls) - case tp: AndType => - tp.tp1.derivesFrom(cls) || tp.tp2.derivesFrom(cls) - case tp: OrType => - tp.tp1.derivesFrom(cls) && tp.tp2.derivesFrom(cls) - case tp: JavaArrayType => - cls == defn.ObjectClass - case _ => - false + final def derivesFrom(cls: Symbol)(implicit ctx: Context): Boolean = { + def loop(tp: Type) = tp match { + case tp: TypeRef => + val sym = tp.symbol + if (sym.isClass) sym.derivesFrom(cls) else tp.underlying.derivesFrom(cls) + case tp: TypeProxy => + tp.underlying.derivesFrom(cls) + case tp: AndType => + tp.tp1.derivesFrom(cls) || tp.tp2.derivesFrom(cls) + case tp: OrType => + tp.tp1.derivesFrom(cls) && tp.tp2.derivesFrom(cls) + case tp: JavaArrayType => + cls == defn.ObjectClass + case _ => + false + } + cls == defn.AnyClass || loop(this) } /** Is this type guaranteed not to have `null` as a value? @@ -448,7 +450,18 @@ object Types { if (rinfo.isAlias) rinfo else if (pdenot.info.isAlias) pdenot.info else if (ctx.pendingMemberSearches.contains(name)) safeAnd(pdenot.info, rinfo) - else pdenot.info & rinfo + else + try pdenot.info & rinfo + catch { + case ex: CyclicReference => + // happens for tests/pos/sets.scala. findMember is called from baseTypeRef. + // The & causes a subtype check which calls baseTypeRef again with the same + // superclass. In the observed case, the superclass was Any, and + // the special shortcut for Any in derivesFrom was as yet absent. To reproduce, + // remove the special treatment of Any in derivesFrom and compile + // sets.scala. + safeAnd(pdenot.info, rinfo) + } pdenot.asSingleDenotation.derivedSingleDenotation(pdenot.symbol, jointInfo) } else pdenot & (new JointRefDenotation(NoSymbol, rinfo, Period.allInRun(ctx.runId)), pre) @@ -856,7 +869,7 @@ object Types { object instantiate extends TypeMap { var isSafe = true def apply(tp: Type): Type = tp match { - case TypeRef(RefinedThis(`pre`), name) if name.isLambdaArgName => + case TypeRef(RefinedThis(`pre`), name) if name.isHkArgName => member(name).info match { case TypeAlias(alias) => alias case _ => isSafe = false; tp @@ -869,7 +882,7 @@ object Types { } } def instArg(tp: Type): Type = tp match { - case tp @ TypeAlias(TypeRef(RefinedThis(`pre`), name)) if name.isLambdaArgName => + case tp @ TypeAlias(TypeRef(RefinedThis(`pre`), name)) if name.isHkArgName => member(name).info match { case TypeAlias(alias) => tp.derivedTypeAlias(alias) // needed to keep variance case bounds => bounds @@ -884,19 +897,17 @@ object Types { instantiate(tp) } /** Reduce rhs of $hkApply to make it stand alone */ - def betaReduce(tp: Type) = - if (pre.parent.isSafeLambda) { - val reduced = instTop(tp) - if (instantiate.isSafe) reduced else NoType - } - else NoType + def betaReduce(tp: Type) = { + val reduced = instTop(tp) + if (instantiate.isSafe) reduced else NoType + } pre.refinedInfo match { case TypeAlias(alias) => if (pre.refinedName ne name) loop(pre.parent) else if (!pre.refinementRefersToThis) alias else alias match { case TypeRef(RefinedThis(`pre`), aliasName) => lookupRefined(aliasName) // (1) - case _ => if (name == tpnme.hkApply) betaReduce(alias) else NoType // (2) + case _ => if (name == tpnme.hkApply) betaReduce(alias) else NoType // (2) // ### use TypeApplication's betaReduce } case _ => loop(pre.parent) } @@ -1375,7 +1386,11 @@ object Types { if (owner.isTerm) d else d.asSeenFrom(prefix) } - private def checkSymAssign(sym: Symbol)(implicit ctx: Context) = + private def checkSymAssign(sym: Symbol)(implicit ctx: Context) = { + def selfTypeOf(sym: Symbol) = sym.owner.info match { + case info: ClassInfo => info.givenSelfType + case _ => NoType + } assert( (lastSymbol eq sym) || (lastSymbol eq null) || { @@ -1387,9 +1402,16 @@ object Types { (lastDefRunId == NoRunId) } || (lastSymbol.infoOrCompleter == ErrorType || - sym.owner.derivesFrom(lastSymbol.owner) && sym.owner != lastSymbol.owner - ), - s"data race? overwriting symbol of ${this.show} / $this / ${this.getClass} / ${lastSymbol.id} / ${sym.id} / ${sym.owner} / ${lastSymbol.owner} / ${ctx.phase} at run ${ctx.runId}") + sym.owner != lastSymbol.owner && + (sym.owner.derivesFrom(lastSymbol.owner) || + selfTypeOf(sym).derivesFrom(lastSymbol.owner) || + selfTypeOf(lastSymbol).derivesFrom(sym.owner))), + s"""data race? overwriting symbol of type ${this.show}, + |long form = $this of class ${this.getClass}, + |last sym id = ${lastSymbol.id}, new sym id = ${sym.id}, + |last owner = ${lastSymbol.owner}, new owner = ${sym.owner}, + |period = ${ctx.phase} at run ${ctx.runId}""".stripMargin) + } protected def sig: Signature = Signature.NotAMethod @@ -1516,7 +1538,12 @@ object Types { else { val res = prefix.lookupRefined(name) if (res.exists) res - else if (name == tpnme.hkApply && prefix.noHK) derivedSelect(prefix.EtaExpandCore) + else if (name == tpnme.hkApply && prefix.classNotLambda) { + // After substitution we might end up with a type like + // `C { type hk$0 = T0; ...; type hk$n = Tn } # $Apply` + // where C is a class. In that case we eta expand `C`. + derivedSelect(prefix.EtaExpandCore(this.prefix.typeConstructor.typeParams)) + } else newLikeThis(prefix) } @@ -1560,6 +1587,15 @@ object Types { case _ => false } + + /* A version of toString which also prints aliases. Can be used for debugging + override def toString = + if (isTerm) s"TermRef($prefix, $name)" + else s"TypeRef($prefix, $name)${ + if (lastDenotation != null && lastDenotation.infoOrCompleter.isAlias) + s"@@@ ${lastDenotation.infoOrCompleter.asInstanceOf[TypeAlias].hi}" + else ""}" + */ } abstract case class TermRef(override val prefix: Type, name: TermName) extends NamedType with SingletonType { @@ -1756,8 +1792,8 @@ object Types { object TypeRef { def checkProjection(prefix: Type, name: TypeName)(implicit ctx: Context) = - if (name == tpnme.hkApply && prefix.noHK) - assert(false, s"bad type : $prefix.$name should not be $$applied") + if (name == tpnme.hkApply && prefix.classNotLambda) + assert(false, s"bad type : $prefix.$name does not allow $$Apply projection") /** Create type ref with given prefix and name */ def apply(prefix: Type, name: TypeName)(implicit ctx: Context): TypeRef = { @@ -1897,20 +1933,15 @@ object Types { override def underlying(implicit ctx: Context) = parent - private def checkInst(implicit ctx: Context): this.type = { - if (Config.checkLambdaVariance) - refinedInfo match { - case refinedInfo: TypeBounds if refinedInfo.variance != 0 && refinedName.isLambdaArgName => - val cls = parent.LambdaClass(forcing = false) - if (cls.exists) - assert(refinedInfo.variance == cls.typeParams.apply(refinedName.LambdaArgIndex).variance, - s"variance mismatch for $this, $cls, ${cls.typeParams}, ${cls.typeParams.apply(refinedName.LambdaArgIndex).variance}, ${refinedInfo.variance}") - case _ => + private def badInst = + throw new AssertionError(s"bad instantiation: $this") + + def checkInst(implicit ctx: Context): this.type = { + if (refinedName == tpnme.hkApply) + parent.stripTypeVar match { + case RefinedType(_, name) if name.isHkArgName => // ok + case _ => badInst } - if (Config.checkProjections && - (refinedName == tpnme.hkApply || refinedName.isLambdaArgName) && - parent.noHK) - assert(false, s"illegal refinement of first-order type: $this") this } @@ -1932,7 +1963,7 @@ object Types { false } override def computeHash = doHash(refinedName, refinedInfo, parent) - override def toString = s"RefinedType($parent, $refinedName, $refinedInfo | $hashCode)" // !!! TODO: remove + override def toString = s"RefinedType($parent, $refinedName, $refinedInfo)" } class CachedRefinedType(parent: Type, refinedName: Name, infoFn: RefinedType => Type) extends RefinedType(parent, refinedName) { @@ -2632,11 +2663,13 @@ object Types { def clsDenot = if (prefix eq cls.owner.thisType) cls.denot else cls.denot.copySymDenotation(info = this) if (typeRefCache == null) typeRefCache = - if ((cls is PackageClass) || cls.owner.isTerm) prefix select cls - else prefix select (cls.name, clsDenot) + if ((cls is PackageClass) || cls.owner.isTerm) symbolicTypeRef + else TypeRef(prefix, cls.name, clsDenot) typeRefCache } + def symbolicTypeRef(implicit ctx: Context): Type = TypeRef(prefix, cls) + // cached because baseType needs parents private var parentsCache: List[TypeRef] = null @@ -2701,8 +2734,12 @@ object Types { case _ => this } - def contains(tp: Type)(implicit ctx: Context) = tp match { + def contains(tp: Type)(implicit ctx: Context): Boolean = tp match { case tp: TypeBounds => lo <:< tp.lo && tp.hi <:< hi + case tp: ClassInfo => + // Note: Taking a normal typeRef does not work here. A normal ref might contain + // also other information about the named type (e.g. bounds). + contains(tp.symbolicTypeRef) case _ => lo <:< tp && tp <:< hi } @@ -2746,9 +2783,9 @@ object Types { abstract class TypeAlias(val alias: Type, override val variance: Int) extends TypeBounds(alias, alias) { /** pre: this is a type alias */ - def derivedTypeAlias(tp: Type, variance: Int = this.variance)(implicit ctx: Context) = - if ((lo eq tp) && (variance == this.variance)) this - else TypeAlias(tp, variance) + def derivedTypeAlias(alias: Type, variance: Int = this.variance)(implicit ctx: Context) = + if ((alias eq this.alias) && (variance == this.variance)) this + else TypeAlias(alias, variance) override def & (that: TypeBounds)(implicit ctx: Context): TypeBounds = { val v = this commonVariance that @@ -3287,7 +3324,7 @@ object Types { } } - class MergeError(msg: String) extends TypeError(msg) + class MergeError(msg: String, val tp1: Type, val tp2: Type) extends TypeError(msg) // ----- Debug --------------------------------------------------------- diff --git a/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/src/dotty/tools/dotc/core/tasty/TreePickler.scala index c0136538b..d11d6f4b7 100644 --- a/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -9,6 +9,7 @@ import Contexts._, Symbols._, Types._, Names._, Constants._, Decorators._, Annot import collection.mutable import NameOps._ import TastyBuffer._ +import TypeApplications._ class TreePickler(pickler: TastyPickler) { val buf = new TreeBuffer @@ -142,6 +143,9 @@ class TreePickler(pickler: TastyPickler) { } def pickleNewType(tpe: Type, richTypes: Boolean): Unit = try { tpe match { + case AppliedType(tycon, args) => + writeByte(APPLIEDtype) + withLength { pickleType(tycon); args.foreach(pickleType(_)) } case ConstantType(value) => pickleConstant(value) case tpe: TypeRef if tpe.info.isAlias && tpe.symbol.is(Flags.AliasPreferred) => @@ -181,15 +185,10 @@ class TreePickler(pickler: TastyPickler) { pickleNameAndSig(tpe.name, tpe.signature); pickleType(tpe.prefix) } case tpe: NamedType => - if (tpe.name == tpnme.hkApply && tpe.prefix.argInfos.nonEmpty && tpe.prefix.isInstantiatedLambda) - // instantiated lambdas are pickled as APPLIEDTYPE; #Apply will - // be reconstituted when unpickling. - pickleType(tpe.prefix) - else if (isLocallyDefined(tpe.symbol)) { + if (isLocallyDefined(tpe.symbol)) { writeByte(if (tpe.isType) TYPEREFsymbol else TERMREFsymbol) pickleSymRef(tpe.symbol); pickleType(tpe.prefix) - } - else { + } else { writeByte(if (tpe.isType) TYPEREF else TERMREF) pickleName(tpe.name); pickleType(tpe.prefix) } @@ -211,18 +210,11 @@ class TreePickler(pickler: TastyPickler) { case tpe: SkolemType => pickleType(tpe.info) case tpe: RefinedType => - val args = tpe.argInfos - if (args.isEmpty) { - writeByte(REFINEDtype) - withLength { - pickleType(tpe.parent) - pickleName(tpe.refinedName) - pickleType(tpe.refinedInfo, richTypes = true) - } - } - else { - writeByte(APPLIEDtype) - withLength { pickleType(tpe.withoutArgs(args)); args.foreach(pickleType(_)) } + writeByte(REFINEDtype) + withLength { + pickleType(tpe.parent) + pickleName(tpe.refinedName) + pickleType(tpe.refinedInfo, richTypes = true) } case tpe: TypeAlias => writeByte(TYPEALIAS) diff --git a/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala b/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala index cf7b487bb..618e3ceea 100644 --- a/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala +++ b/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala @@ -21,6 +21,7 @@ import typer.Mode import PickleBuffer._ import scala.reflect.internal.pickling.PickleFormat._ import Decorators._ +import TypeApplications._ import classfile.ClassfileParser import scala.collection.{ mutable, immutable } import scala.collection.mutable.ListBuffer @@ -52,7 +53,7 @@ object Scala2Unpickler { case TempPolyType(tparams, restpe) => if (denot.isType) { assert(!denot.isClass) - restpe.LambdaAbstract(tparams, cycleParanoid = true) + restpe.LambdaAbstract(tparams) } else PolyType.fromSymbols(tparams, restpe) @@ -142,51 +143,6 @@ object Scala2Unpickler { denot.info = ClassInfo( // final info denot.owner.thisType, denot.classSymbol, parentRefs, declsInRightOrder, ost) } - - /** Adapt arguments to type parameters so that variance of type lambda arguments - * agrees with variance of corresponding higherkinded type parameters. Example: - * - * class Companion[+CC[X]] - * Companion[List] - * - * with adaptArgs, this will expand to - * - * Companion[[X] => List[X]] - * - * instead of - * - * Companion[[+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 `Companion[GenTraversable]` - * or `Companion[ListBuffer]`. Without the adaptation we would end up with - * - * Companion[[+X] => GenTraversable[X]] - * Companion[[X] => List[X]] - * - * and the second is not a subtype of the first. So if we have overridding memebrs of the two - * types we get an error. - */ - def adaptArgs(tparams: List[Symbol], args: List[Type])(implicit ctx: Context): List[Type] = tparams match { - case tparam :: tparams1 => - val boundLambda = tparam.infoOrCompleter match { - case TypeBounds(_, hi) => hi.LambdaClass(forcing = false) - case _ => NoSymbol - } - def adaptArg(arg: Type): Type = arg match { - case arg: TypeRef if arg.symbol.isLambdaTrait => - assert(arg.symbol.typeParams.length == boundLambda.typeParams.length) - arg.prefix.select(boundLambda) - case arg: RefinedType => - arg.derivedRefinedType(adaptArg(arg.parent), arg.refinedName, arg.refinedInfo) - case _ => - arg - } - val arg = args.head - val adapted = if (boundLambda.exists) adaptArg(arg) else arg - adapted :: adaptArgs(tparams1, args.tail) - case nil => args - } } /** Unpickle symbol table information descending from a class and/or module root @@ -759,8 +715,9 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas else TypeRef(pre, sym.name.asTypeName) val args = until(end, readTypeRef) if (sym == defn.ByNameParamClass2x) ExprType(args.head) - else if (args.isEmpty && sym.typeParams.nonEmpty) tycon.EtaExpand(sym.typeParams) - else tycon.appliedTo(adaptArgs(sym.typeParams, args)) + else if (args.nonEmpty) tycon.safeAppliedTo(etaExpandIfHK(sym.typeParams, args)) + else if (sym.typeParams.nonEmpty) tycon.EtaExpand(sym.typeParams) + else tycon case TYPEBOUNDStpe => TypeBounds(readTypeRef(), readTypeRef()) case REFINEDtpe => |