diff options
Diffstat (limited to 'compiler/src/dotty/tools/dotc/core/TypeComparer.scala')
-rw-r--r-- | compiler/src/dotty/tools/dotc/core/TypeComparer.scala | 1502 |
1 files changed, 1502 insertions, 0 deletions
diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala new file mode 100644 index 000000000..f78820fff --- /dev/null +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -0,0 +1,1502 @@ +package dotty.tools +package dotc +package core + +import Types._, Contexts._, Symbols._, Flags._, Names._, NameOps._, Denotations._ +import Decorators._ +import StdNames.{nme, tpnme} +import collection.mutable +import util.{Stats, DotClass, SimpleMap} +import config.Config +import config.Printers.{typr, constr, subtyping, noPrinter} +import TypeErasure.{erasedLub, erasedGlb} +import TypeApplications._ +import scala.util.control.NonFatal + +/** Provides methods to compare types. + */ +class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { + implicit val ctx: Context = initctx + + val state = ctx.typerState + import state.constraint + + private var pendingSubTypes: mutable.Set[(Type, Type)] = null + private var recCount = 0 + + private var needsGc = false + + /** Is a subtype check in progress? In that case we may not + * permanently instantiate type variables, because the corresponding + * constraint might still be retracted and the instantiation should + * then be reversed. + */ + def subtypeCheckInProgress: Boolean = { + val result = recCount > 0 + if (result) { + constr.println("*** needsGC ***") + needsGc = true + } + result + } + + /** For statistics: count how many isSubTypes are part of successful comparisons */ + private var successCount = 0 + private var totalCount = 0 + + private var myAnyClass: ClassSymbol = null + private var myNothingClass: ClassSymbol = null + private var myNullClass: ClassSymbol = null + private var myObjectClass: ClassSymbol = null + private var myAnyType: TypeRef = null + private var myNothingType: TypeRef = null + + def AnyClass = { + if (myAnyClass == null) myAnyClass = defn.AnyClass + myAnyClass + } + def NothingClass = { + if (myNothingClass == null) myNothingClass = defn.NothingClass + myNothingClass + } + def NullClass = { + if (myNullClass == null) myNullClass = defn.NullClass + myNullClass + } + def ObjectClass = { + if (myObjectClass == null) myObjectClass = defn.ObjectClass + myObjectClass + } + def AnyType = { + if (myAnyType == null) myAnyType = AnyClass.typeRef + myAnyType + } + def NothingType = { + if (myNothingType == null) myNothingType = NothingClass.typeRef + myNothingType + } + + /** Indicates whether a previous subtype check used GADT bounds */ + var GADTused = false + + /** Record that GADT bounds of `sym` were used in a subtype check. + * But exclude constructor type parameters, as these are aliased + * to the corresponding class parameters, which does not constitute + * a true usage of a GADT symbol. + */ + private def GADTusage(sym: Symbol) = { + if (!sym.owner.isConstructor) GADTused = true + true + } + + // Subtype testing `<:<` + + def topLevelSubType(tp1: Type, tp2: Type): Boolean = { + if (tp2 eq NoType) return false + if ((tp2 eq tp1) || (tp2 eq WildcardType)) return true + try isSubType(tp1, tp2) + finally + if (Config.checkConstraintsSatisfiable) + assert(isSatisfiable, constraint.show) + } + + protected def isSubType(tp1: Type, tp2: Type): Boolean = ctx.traceIndented(s"isSubType ${traceInfo(tp1, tp2)}", subtyping) { + if (tp2 eq NoType) false + else if (tp1 eq tp2) true + else { + val saved = constraint + val savedSuccessCount = successCount + try { + recCount = recCount + 1 + val result = + if (recCount < Config.LogPendingSubTypesThreshold) firstTry(tp1, tp2) + else monitoredIsSubType(tp1, tp2) + recCount = recCount - 1 + if (!result) constraint = saved + else if (recCount == 0 && needsGc) { + state.gc() + needsGc = false + } + if (Stats.monitored) recordStatistics(result, savedSuccessCount) + result + } catch { + case NonFatal(ex) => + if (ex.isInstanceOf[AssertionError]) showGoal(tp1, tp2) + recCount -= 1 + constraint = saved + successCount = savedSuccessCount + throw ex + } + } + } + + private def monitoredIsSubType(tp1: Type, tp2: Type) = { + if (pendingSubTypes == null) { + pendingSubTypes = new mutable.HashSet[(Type, Type)] + ctx.log(s"!!! deep subtype recursion involving ${tp1.show} <:< ${tp2.show}, constraint = ${state.constraint.show}") + ctx.log(s"!!! constraint = ${constraint.show}") + //if (ctx.settings.YnoDeepSubtypes.value) { + // new Error("deep subtype").printStackTrace() + //} + assert(!ctx.settings.YnoDeepSubtypes.value) + if (Config.traceDeepSubTypeRecursions && !this.isInstanceOf[ExplainingTypeComparer]) + ctx.log(TypeComparer.explained(implicit ctx => ctx.typeComparer.isSubType(tp1, tp2))) + } + val p = (tp1, tp2) + !pendingSubTypes(p) && { + try { + pendingSubTypes += p + firstTry(tp1, tp2) + } finally { + pendingSubTypes -= p + } + } + } + + private def firstTry(tp1: Type, tp2: Type): Boolean = tp2 match { + case tp2: NamedType => + def compareNamed(tp1: Type, tp2: NamedType): Boolean = { + implicit val ctx: Context = this.ctx + tp2.info match { + case info2: TypeAlias => isSubType(tp1, info2.alias) + case _ => tp1 match { + case tp1: NamedType => + tp1.info match { + case info1: TypeAlias => + if (isSubType(info1.alias, tp2)) return true + if (tp1.prefix.isStable) return false + // If tp1.prefix is stable, the alias does contain all information about the original ref, so + // there's no need to try something else. (This is important for performance). + // To see why we cannot in general stop here, consider: + // + // trait C { type A } + // trait D { type A = String } + // (C & D)#A <: C#A + // + // Following the alias leads to the judgment `String <: C#A` which is false. + // However the original judgment should be true. + case _ => + } + val sym1 = + if (tp1.symbol.is(ModuleClass) && tp2.symbol.is(ModuleVal)) + // For convenience we want X$ <:< X.type + // This is safe because X$ self-type is X.type + tp1.symbol.companionModule + else + 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] + ) || + thirdTryNamed(tp1, tp2) + case _ => + secondTry(tp1, tp2) + } + } + } + compareNamed(tp1, tp2) + case tp2: ProtoType => + isMatchedByProto(tp2, tp1) + case tp2: BoundType => + tp2 == tp1 || secondTry(tp1, tp2) + case tp2: TypeVar => + isSubType(tp1, tp2.underlying) + case tp2: WildcardType => + def compareWild = tp2.optBounds match { + case TypeBounds(_, hi) => isSubType(tp1, hi) + case NoType => true + } + compareWild + case tp2: LazyRef => + !tp2.evaluating && isSubType(tp1, tp2.ref) + case tp2: AnnotatedType => + isSubType(tp1, tp2.tpe) // todo: refine? + case tp2: ThisType => + def compareThis = { + val cls2 = tp2.cls + tp1 match { + case tp1: ThisType => + // We treat two prefixes A.this, B.this as equivalent if + // A's selftype derives from B and B's selftype derives from A. + val cls1 = tp1.cls + cls1.classInfo.selfType.derivesFrom(cls2) && + cls2.classInfo.selfType.derivesFrom(cls1) + case tp1: NamedType if cls2.is(Module) && cls2.eq(tp1.widen.typeSymbol) => + cls2.isStaticOwner || + isSubType(tp1.prefix, cls2.owner.thisType) || + secondTry(tp1, tp2) + case _ => + secondTry(tp1, tp2) + } + } + compareThis + case tp2: SuperType => + def compareSuper = tp1 match { + case tp1: SuperType => + isSubType(tp1.thistpe, tp2.thistpe) && + isSameType(tp1.supertpe, tp2.supertpe) + case _ => + secondTry(tp1, tp2) + } + compareSuper + case AndType(tp21, tp22) => + isSubType(tp1, tp21) && isSubType(tp1, tp22) + case OrType(tp21, tp22) => + if (tp21.stripTypeVar eq tp22.stripTypeVar) isSubType(tp1, tp21) + else secondTry(tp1, tp2) + case TypeErasure.ErasedValueType(tycon1, underlying2) => + def compareErasedValueType = tp1 match { + case TypeErasure.ErasedValueType(tycon2, underlying1) => + (tycon1.symbol eq tycon2.symbol) && isSameType(underlying1, underlying2) + case _ => + secondTry(tp1, tp2) + } + compareErasedValueType + case ErrorType => + true + case _ => + secondTry(tp1, tp2) + } + + private def secondTry(tp1: Type, tp2: Type): Boolean = tp1 match { + case tp1: NamedType => + tp1.info match { + case info1: TypeAlias => + if (isSubType(info1.alias, tp2)) return true + if (tp1.prefix.isStable) return false + case _ => + } + thirdTry(tp1, tp2) + case tp1: PolyParam => + def flagNothingBound = { + if (!frozenConstraint && tp2.isRef(defn.NothingClass) && state.isGlobalCommittable) { + def msg = s"!!! instantiated to Nothing: $tp1, constraint = ${constraint.show}" + if (Config.failOnInstantiationToNothing) assert(false, msg) + else ctx.log(msg) + } + true + } + def comparePolyParam = + ctx.mode.is(Mode.TypevarsMissContext) || + isSubTypeWhenFrozen(bounds(tp1).hi, tp2) || { + if (canConstrain(tp1)) addConstraint(tp1, tp2, fromBelow = false) && flagNothingBound + else thirdTry(tp1, tp2) + } + comparePolyParam + case tp1: ThisType => + val cls1 = tp1.cls + tp2 match { + case tp2: TermRef if cls1.is(Module) && cls1.eq(tp2.widen.typeSymbol) => + cls1.isStaticOwner || + isSubType(cls1.owner.thisType, tp2.prefix) || + thirdTry(tp1, tp2) + case _ => + thirdTry(tp1, tp2) + } + case tp1: SkolemType => + tp2 match { + case tp2: SkolemType if !ctx.phase.isTyper && tp1.info <:< tp2.info => true + case _ => thirdTry(tp1, tp2) + } + case tp1: TypeVar => + isSubType(tp1.underlying, tp2) + case tp1: WildcardType => + def compareWild = tp1.optBounds match { + case TypeBounds(lo, _) => isSubType(lo, tp2) + case _ => true + } + compareWild + case tp1: LazyRef => + // If `tp1` is in train of being evaluated, don't force it + // because that would cause an assertionError. Return false instead. + // See i859.scala for an example where we hit this case. + !tp1.evaluating && isSubType(tp1.ref, tp2) + case tp1: AnnotatedType => + isSubType(tp1.tpe, tp2) + case AndType(tp11, tp12) => + if (tp11.stripTypeVar eq tp12.stripTypeVar) isSubType(tp11, tp2) + else thirdTry(tp1, tp2) + case tp1 @ OrType(tp11, tp12) => + def joinOK = tp2.dealias match { + case tp12: HKApply => + // If we apply the default algorithm for `A[X] | B[Y] <: C[Z]` where `C` is a + // type parameter, we will instantiate `C` to `A` and then fail when comparing + // with `B[Y]`. To do the right thing, we need to instantiate `C` to the + // common superclass of `A` and `B`. + isSubType(tp1.join, tp2) + case _ => + false + } + joinOK || isSubType(tp11, tp2) && isSubType(tp12, tp2) + case ErrorType => + true + case _ => + thirdTry(tp1, tp2) + } + + private def thirdTryNamed(tp1: Type, tp2: NamedType): Boolean = tp2.info match { + case TypeBounds(lo2, _) => + def compareGADT: Boolean = { + val gbounds2 = ctx.gadt.bounds(tp2.symbol) + (gbounds2 != null) && + (isSubTypeWhenFrozen(tp1, gbounds2.lo) || + narrowGADTBounds(tp2, tp1, isUpper = false)) && + GADTusage(tp2.symbol) + } + ((frozenConstraint || !isCappable(tp1)) && isSubType(tp1, lo2) || + compareGADT || + fourthTry(tp1, tp2)) + + case _ => + val cls2 = tp2.symbol + if (cls2.isClass) { + val base = tp1.baseTypeRef(cls2) + if (base.exists && (base ne tp1)) return isSubType(base, tp2) + if (cls2 == defn.SingletonClass && tp1.isStable) return true + } + fourthTry(tp1, tp2) + } + + private def thirdTry(tp1: Type, tp2: Type): Boolean = tp2 match { + case tp2: NamedType => + thirdTryNamed(tp1, tp2) + case tp2: PolyParam => + def comparePolyParam = + (ctx.mode is Mode.TypevarsMissContext) || + isSubTypeWhenFrozen(tp1, bounds(tp2).lo) || { + if (canConstrain(tp2)) addConstraint(tp2, tp1.widenExpr, fromBelow = true) + else fourthTry(tp1, tp2) + } + comparePolyParam + case tp2: RefinedType => + def compareRefinedSlow: Boolean = { + val name2 = tp2.refinedName + isSubType(tp1, tp2.parent) && + (name2 == nme.WILDCARD || hasMatchingMember(name2, tp1, tp2)) + } + def compareRefined: Boolean = { + val tp1w = tp1.widen + val skipped2 = skipMatching(tp1w, tp2) + 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) + } + else // fast path, in particular for refinements resulting from parameterization. + isSubRefinements(tp1w.asInstanceOf[RefinedType], tp2, skipped2) && + isSubType(tp1, skipped2) + } + compareRefined + case tp2: RecType => + def compareRec = tp1.safeDealias match { + case tp1: RecType => + val rthis1 = RecThis(tp1) + isSubType(tp1.parent, tp2.parent.substRecThis(tp2, rthis1)) + case _ => + val tp1stable = ensureStableSingleton(tp1) + isSubType(fixRecs(tp1stable, tp1stable.widenExpr), tp2.parent.substRecThis(tp2, tp1stable)) + } + compareRec + case tp2 @ HKApply(tycon2, args2) => + compareHkApply2(tp1, tp2, tycon2, args2) + case tp2 @ PolyType(tparams2, body2) => + def compareHkLambda: Boolean = tp1.stripTypeVar match { + case tp1 @ PolyType(tparams1, body1) => + /* Don't compare bounds of lambdas under language:Scala2, or t2994 will fail + * The issue is that, logically, bounds should compare contravariantly, + * but that would invalidate a pattern exploited in t2994: + * + * [X0 <: Number] -> Number <:< [X0] -> Any + * + * Under the new scheme, `[X0] -> Any` is NOT a kind that subsumes + * all other bounds. You'd have to write `[X0 >: Any <: Nothing] -> Any` instead. + * This might look weird, but is the only logically correct way to do it. + * + * Note: it would be nice if this could trigger a migration warning, but I + * am not sure how, since the code is buried so deep in subtyping logic. + */ + def boundsOK = + ctx.scala2Mode || + tparams1.corresponds(tparams2)((tparam1, tparam2) => + isSubType(tparam2.paramBounds.subst(tp2, tp1), tparam1.paramBounds)) + val saved = comparedPolyTypes + comparedPolyTypes += tp1 + comparedPolyTypes += tp2 + try + variancesConform(tparams1, tparams2) && + boundsOK && + isSubType(body1, body2.subst(tp2, tp1)) + finally comparedPolyTypes = saved + case _ => + if (!tp1.isHK) { + tp2 match { + case EtaExpansion(tycon2) if tycon2.symbol.isClass => + return isSubType(tp1, tycon2) + case _ => + } + } + fourthTry(tp1, tp2) + } + compareHkLambda + case OrType(tp21, tp22) => + // Rewrite T1 <: (T211 & T212) | T22 to T1 <: (T211 | T22) and T1 <: (T212 | T22) + // and analogously for T1 <: T21 | (T221 & T222) + // `|' types to the right of <: are problematic, because + // we have to choose one constraint set or another, which might cut off + // solutions. The rewriting delays the point where we have to choose. + tp21 match { + case AndType(tp211, tp212) => + return isSubType(tp1, OrType(tp211, tp22)) && isSubType(tp1, OrType(tp212, tp22)) + case _ => + } + tp22 match { + case AndType(tp221, tp222) => + return isSubType(tp1, OrType(tp21, tp221)) && isSubType(tp1, OrType(tp21, tp222)) + case _ => + } + either(isSubType(tp1, tp21), isSubType(tp1, tp22)) || fourthTry(tp1, tp2) + case tp2 @ MethodType(_, formals2) => + def compareMethod = tp1 match { + case tp1 @ MethodType(_, formals1) => + (tp1.signature consistentParams tp2.signature) && + matchingParams(formals1, formals2, tp1.isJava, tp2.isJava) && + tp1.isImplicit == tp2.isImplicit && // needed? + isSubType(tp1.resultType, tp2.resultType.subst(tp2, tp1)) + case _ => + false + } + compareMethod + case tp2 @ ExprType(restpe2) => + def compareExpr = tp1 match { + // We allow ()T to be a subtype of => T. + // We need some subtype relationship between them so that e.g. + // def toString and def toString() don't clash when seen + // as members of the same type. And it seems most logical to take + // ()T <:< => T, since everything one can do with a => T one can + // also do with a ()T by automatic () insertion. + case tp1 @ MethodType(Nil, _) => isSubType(tp1.resultType, restpe2) + case _ => isSubType(tp1.widenExpr, restpe2) + } + compareExpr + case tp2 @ TypeBounds(lo2, hi2) => + def compareTypeBounds = tp1 match { + case tp1 @ TypeBounds(lo1, hi1) => + (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 => + tp2 contains tp1 + case _ => + false + } + compareTypeBounds + case ClassInfo(pre2, cls2, _, _, _) => + def compareClassInfo = tp1 match { + case ClassInfo(pre1, cls1, _, _, _) => + (cls1 eq cls2) && isSubType(pre1, pre2) + case _ => + false + } + compareClassInfo + case _ => + fourthTry(tp1, tp2) + } + + private def fourthTry(tp1: Type, tp2: Type): Boolean = tp1 match { + case tp1: TypeRef => + tp1.info match { + case TypeBounds(_, hi1) => + def compareGADT = { + val gbounds1 = ctx.gadt.bounds(tp1.symbol) + (gbounds1 != null) && + (isSubTypeWhenFrozen(gbounds1.hi, tp2) || + narrowGADTBounds(tp1, tp2, isUpper = true)) && + GADTusage(tp1.symbol) + } + isSubType(hi1, tp2) || compareGADT + case _ => + def isNullable(tp: Type): Boolean = tp.widenDealias match { + case tp: TypeRef => tp.symbol.isNullableClass + case tp: RefinedOrRecType => isNullable(tp.parent) + case AndType(tp1, tp2) => isNullable(tp1) && isNullable(tp2) + case OrType(tp1, tp2) => isNullable(tp1) || isNullable(tp2) + case _ => false + } + (tp1.symbol eq NothingClass) && tp2.isValueTypeOrLambda || + (tp1.symbol eq NullClass) && isNullable(tp2) + } + case tp1: SingletonType => + /** if `tp2 == p.type` and `p: q.type` then try `tp1 <:< q.type` as a last effort.*/ + def comparePaths = tp2 match { + case tp2: TermRef => + tp2.info.widenExpr match { + case tp2i: SingletonType => + isSubType(tp1, tp2i) // see z1720.scala for a case where this can arise even in typer. + case _ => false + } + case _ => + false + } + isNewSubType(tp1.underlying.widenExpr, tp2) || comparePaths + case tp1: RefinedType => + isNewSubType(tp1.parent, tp2) + case tp1: RecType => + isNewSubType(tp1.parent, tp2) + case tp1 @ HKApply(tycon1, args1) => + compareHkApply1(tp1, tycon1, args1, tp2) + case EtaExpansion(tycon1) => + isSubType(tycon1, tp2) + 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 + // `&' types to the left of <: are problematic, because + // we have to choose one constraint set or another, which might cut off + // solutions. The rewriting delays the point where we have to choose. + tp11 match { + case OrType(tp111, tp112) => + return isSubType(AndType(tp111, tp12), tp2) && isSubType(AndType(tp112, tp12), tp2) + case _ => + } + tp12 match { + case OrType(tp121, tp122) => + return isSubType(AndType(tp11, tp121), tp2) && isSubType(AndType(tp11, tp122), tp2) + case _ => + } + either(isSubType(tp11, tp2), isSubType(tp12, tp2)) + case JavaArrayType(elem1) => + def compareJavaArray = tp2 match { + case JavaArrayType(elem2) => isSubType(elem1, elem2) + case _ => tp2 isRef ObjectClass + } + compareJavaArray + case tp1: ExprType if ctx.phase.id > ctx.gettersPhase.id => + // getters might have converted T to => T, need to compensate. + isSubType(tp1.widenExpr, tp2) + case _ => + false + } + + /** Subtype test for the hk application `tp2 = tycon2[args2]`. + */ + def compareHkApply2(tp1: Type, tp2: HKApply, tycon2: Type, args2: List[Type]): Boolean = { + val tparams = tycon2.typeParams + if (tparams.isEmpty) return false // can happen for ill-typed programs, e.g. neg/tcpoly_overloaded.scala + + /** True if `tp1` and `tp2` have compatible type constructors and their + * corresponding arguments are subtypes relative to their variance (see `isSubArgs`). + */ + def isMatchingApply(tp1: Type): Boolean = tp1 match { + case HKApply(tycon1, args1) => + tycon1.dealias match { + case tycon1: PolyParam => + (tycon1 == tycon2 || + canConstrain(tycon1) && tryInstantiate(tycon1, tycon2)) && + isSubArgs(args1, args2, tparams) + case tycon1: TypeRef => + tycon2.dealias match { + case tycon2: TypeRef if tycon1.symbol == tycon2.symbol => + isSubType(tycon1.prefix, tycon2.prefix) && + isSubArgs(args1, args2, tparams) + case _ => + false + } + case tycon1: TypeVar => + isMatchingApply(tycon1.underlying) + case tycon1: AnnotatedType => + isMatchingApply(tycon1.underlying) + case _ => + false + } + case _ => + false + } + + /** `param2` can be instantiated to a type application prefix of the LHS + * or to a type application prefix of one of the LHS base class instances + * and the resulting type application is a supertype of `tp1`, + * or fallback to fourthTry. + */ + def canInstantiate(tycon2: PolyParam): Boolean = { + + /** Let + * + * `tparams_1, ..., tparams_k-1` be the type parameters of the rhs + * `tparams1_1, ..., tparams1_n-1` be the type parameters of the constructor of the lhs + * `args1_1, ..., args1_n-1` be the type arguments of the lhs + * `d = n - k` + * + * Returns `true` iff `d >= 0` and `tycon2` can be instantiated to + * + * [tparams1_d, ... tparams1_n-1] -> tycon1a[args_1, ..., args_d-1, tparams_d, ... tparams_n-1] + * + * such that the resulting type application is a supertype of `tp1`. + */ + def tyconOK(tycon1a: Type, args1: List[Type]) = { + var tycon1b = tycon1a + val tparams1a = tycon1a.typeParams + val lengthDiff = tparams1a.length - tparams.length + lengthDiff >= 0 && { + val tparams1 = tparams1a.drop(lengthDiff) + variancesConform(tparams1, tparams) && { + if (lengthDiff > 0) + tycon1b = PolyType(tparams1.map(_.paramName), tparams1.map(_.paramVariance))( + tl => tparams1.map(tparam => tl.lifted(tparams, tparam.paramBounds).bounds), + tl => tycon1a.appliedTo(args1.take(lengthDiff) ++ + tparams1.indices.toList.map(PolyParam(tl, _)))) + (ctx.mode.is(Mode.TypevarsMissContext) || + tryInstantiate(tycon2, tycon1b.ensureHK)) && + isSubType(tp1, tycon1b.appliedTo(args2)) + } + } + } + + tp1.widen match { + case tp1w @ HKApply(tycon1, args1) => + tyconOK(tycon1, args1) + case tp1w => + tp1w.typeSymbol.isClass && { + val classBounds = tycon2.classSymbols + def liftToBase(bcs: List[ClassSymbol]): Boolean = bcs match { + case bc :: bcs1 => + classBounds.exists(bc.derivesFrom) && + tyconOK(tp1w.baseTypeRef(bc), tp1w.baseArgInfos(bc)) || + liftToBase(bcs1) + case _ => + false + } + liftToBase(tp1w.baseClasses) + } || + fourthTry(tp1, tp2) + } + } + + /** Fall back to comparing either with `fourthTry` or against the lower + * approximation of the rhs. + * @param tyconLo The type constructor's lower approximation. + */ + def fallback(tyconLo: Type) = + either(fourthTry(tp1, tp2), isSubType(tp1, tyconLo.applyIfParameterized(args2))) + + /** Let `tycon2bounds` be the bounds of the RHS type constructor `tycon2`. + * Let `app2 = tp2` where the type constructor of `tp2` is replaced by + * `tycon2bounds.lo`. + * If both bounds are the same, continue with `tp1 <:< app2`. + * otherwise continue with either + * + * tp1 <:< tp2 using fourthTry (this might instantiate params in tp1) + * tp1 <:< app2 using isSubType (this might instantiate params in tp2) + */ + def compareLower(tycon2bounds: TypeBounds, tyconIsTypeRef: Boolean): Boolean = + if (tycon2bounds.lo eq tycon2bounds.hi) + isSubType(tp1, + if (tyconIsTypeRef) tp2.superType + else tycon2bounds.lo.applyIfParameterized(args2)) + else + fallback(tycon2bounds.lo) + + tycon2 match { + case param2: PolyParam => + isMatchingApply(tp1) || { + if (canConstrain(param2)) canInstantiate(param2) + else compareLower(bounds(param2), tyconIsTypeRef = false) + } + case tycon2: TypeRef => + isMatchingApply(tp1) || + compareLower(tycon2.info.bounds, tyconIsTypeRef = true) + case _: TypeVar | _: AnnotatedType => + isSubType(tp1, tp2.superType) + case tycon2: HKApply => + fallback(tycon2.lowerBound) + case _ => + false + } + } + + /** Subtype test for the hk application `tp1 = tycon1[args1]`. + */ + def compareHkApply1(tp1: HKApply, tycon1: Type, args1: List[Type], tp2: Type): Boolean = + tycon1 match { + case param1: PolyParam => + def canInstantiate = tp2 match { + case AppliedType(tycon2, args2) => + tryInstantiate(param1, tycon2.ensureHK) && isSubArgs(args1, args2, tycon2.typeParams) + case _ => + false + } + canConstrain(param1) && canInstantiate || + isSubType(bounds(param1).hi.applyIfParameterized(args1), tp2) + case tycon1: TypeProxy => + isSubType(tp1.superType, tp2) + case _ => + false + } + + /** Subtype test for corresponding arguments in `args1`, `args2` according to + * variances in type parameters `tparams`. + */ + def isSubArgs(args1: List[Type], args2: List[Type], tparams: List[TypeParamInfo]): Boolean = + if (args1.isEmpty) args2.isEmpty + else args2.nonEmpty && { + val v = tparams.head.paramVariance + (v > 0 || isSubType(args2.head, args1.head)) && + (v < 0 || isSubType(args1.head, args2.head)) + } && isSubArgs(args1.tail, args2.tail, tparams) + + /** 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[TypeParamInfo], p: Type => Boolean): Boolean = { + val classBounds = tp2.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) + } + + /** Replace any top-level recursive type `{ z => T }` in `tp` with + * `[z := anchor]T`. + */ + private def fixRecs(anchor: SingletonType, tp: Type): Type = { + def fix(tp: Type): Type = tp.stripTypeVar match { + case tp: RecType => fix(tp.parent).substRecThis(tp, anchor) + case tp @ RefinedType(parent, rname, rinfo) => tp.derivedRefinedType(fix(parent), rname, rinfo) + case tp: PolyParam => fixOrElse(bounds(tp).hi, tp) + case tp: TypeProxy => fixOrElse(tp.underlying, tp) + case tp: AndOrType => tp.derivedAndOrType(fix(tp.tp1), fix(tp.tp2)) + case tp => tp + } + def fixOrElse(tp: Type, fallback: Type) = { + val tp1 = fix(tp) + if (tp1 ne tp) tp1 else fallback + } + fix(tp) + } + + /** Returns true iff the result of evaluating either `op1` or `op2` is true, + * trying at the same time to keep the constraint as wide as possible. + * E.g, if + * + * tp11 <:< tp12 = true with post-constraint c1 + * tp12 <:< tp22 = true with post-constraint c2 + * + * and c1 subsumes c2, then c2 is kept as the post-constraint of the result, + * otherwise c1 is kept. + * + * This method is used to approximate a solution in one of the following cases + * + * T1 & T2 <:< T3 + * T1 <:< T2 | T3 + * + * In the first case (the second one is analogous), we have a choice whether we + * want to establish the subtyping judgement using + * + * T1 <:< T3 or T2 <:< T3 + * + * as a precondition. Either precondition might constrain type variables. + * The purpose of this method is to pick the precondition that constrains less. + * The method is not complete, because sometimes there is no best solution. Example: + * + * A? & B? <: T + * + * Here, each precondition leads to a different constraint, and neither of + * the two post-constraints subsumes the other. + */ + private def either(op1: => Boolean, op2: => Boolean): Boolean = { + val preConstraint = constraint + op1 && { + val leftConstraint = constraint + constraint = preConstraint + if (!(op2 && subsumes(leftConstraint, constraint, preConstraint))) { + if (constr != noPrinter && !subsumes(constraint, leftConstraint, preConstraint)) + constr.println(i"CUT - prefer $leftConstraint over $constraint") + constraint = leftConstraint + } + true + } || op2 + } + + /** Like tp1 <:< tp2, but returns false immediately if we know that + * the case was covered previously during subtyping. + */ + private def isNewSubType(tp1: Type, tp2: Type): Boolean = + if (isCovered(tp1) && isCovered(tp2)) { + //println(s"useless subtype: $tp1 <:< $tp2") + false + } else isSubType(tp1, tp2) + + /** Does type `tp1` have a member with name `name` whose normalized type is a subtype of + * the normalized type of the refinement `tp2`? + * Normalization is as follows: If `tp2` contains a skolem to its refinement type, + * rebase both itself and the member info of `tp` on a freshly created skolem type. + */ + protected def hasMatchingMember(name: Name, tp1: Type, tp2: RefinedType): Boolean = { + val rinfo2 = tp2.refinedInfo + val mbr = tp1.member(name) + + def qualifies(m: SingleDenotation) = isSubType(m.info, rinfo2) + + def memberMatches: Boolean = mbr match { // inlined hasAltWith for performance + case mbr: SingleDenotation => qualifies(mbr) + case _ => mbr hasAltWith qualifies + } + + // special case for situations like: + // class C { type T } + // val foo: C + // foo.type <: C { type T {= , <: , >:} foo.T } + def selfReferentialMatch = tp1.isInstanceOf[SingletonType] && { + rinfo2 match { + case rinfo2: TypeBounds => + val mbr1 = tp1.select(name) + !defn.isBottomType(tp1.widen) && + (mbr1 =:= rinfo2.hi || (rinfo2.hi ne rinfo2.lo) && mbr1 =:= rinfo2.lo) + case _ => false + } + } + + /*>|>*/ ctx.traceIndented(i"hasMatchingMember($tp1 . $name :? ${tp2.refinedInfo}) ${mbr.info.show} $rinfo2", subtyping) /*<|<*/ { + memberMatches || selfReferentialMatch + } + } + + final def ensureStableSingleton(tp: Type): SingletonType = tp.stripTypeVar match { + case tp: SingletonType if tp.isStable => tp + case tp: ValueType => SkolemType(tp) + case tp: TypeProxy => ensureStableSingleton(tp.underlying) + } + + /** Skip refinements in `tp2` which match corresponding refinements in `tp1`. + * "Match" means: + * - they appear in the same order, + * - they refine the same names, + * - the refinement in `tp1` is an alias type, and + * - neither refinement refers back to the refined type via a refined this. + * @return The parent type of `tp2` after skipping the matching refinements. + */ + private def skipMatching(tp1: Type, tp2: RefinedType): Type = tp1 match { + case tp1 @ RefinedType(parent1, name1, rinfo1: TypeAlias) if name1 == tp2.refinedName => + tp2.parent match { + case parent2: RefinedType => skipMatching(parent1, parent2) + case parent2 => parent2 + } + case _ => tp2 + } + + /** Are refinements in `tp1` pairwise subtypes of the refinements of `tp2` + * up to parent type `limit`? + * @pre `tp1` has the necessary number of refinements, they are type aliases, + * and their names match the corresponding refinements in `tp2`. + * 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 = { + 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 + * combiners are RefinedTypes, RecTypes, AndTypes or AnnotatedTypes. + * One exception: Refinements referring to basetype args are never considered + * to be already covered. This is necessary because such refined types might + * still need to be compared with a compareAliasRefined. + */ + private def isCovered(tp: Type): Boolean = tp.dealias.stripTypeVar match { + case tp: TypeRef => tp.symbol.isClass && tp.symbol != NothingClass && tp.symbol != NullClass + case tp: ProtoType => false + case tp: RefinedOrRecType => isCovered(tp.parent) + case tp: AnnotatedType => isCovered(tp.underlying) + case AndType(tp1, tp2) => isCovered(tp1) && isCovered(tp2) + case _ => false + } + + /** Defer constraining type variables when compared against prototypes */ + def isMatchedByProto(proto: ProtoType, tp: Type) = tp.stripTypeVar match { + case tp: PolyParam if constraint contains tp => true + case _ => proto.isMatchedBy(tp) + } + + /** Can type `tp` be constrained from above by adding a constraint to + * a typevar that it refers to? In that case we have to be careful not + * to approximate with the lower bound of a type in `thirdTry`. Instead, + * we should first unroll `tp1` until we hit the type variable and bind the + * type variable with (the corresponding type in) `tp2` instead. + */ + private def isCappable(tp: Type): Boolean = tp match { + case tp: PolyParam => constraint contains tp + case tp: TypeProxy => isCappable(tp.underlying) + case tp: AndOrType => isCappable(tp.tp1) || isCappable(tp.tp2) + case _ => false + } + + /** 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. + */ + private def narrowGADTBounds(tr: NamedType, bound: Type, isUpper: Boolean): Boolean = + ctx.mode.is(Mode.GADTflexible) && !frozenConstraint && { + val tparam = tr.symbol + typr.println(i"narrow gadt bound of $tparam: ${tparam.info} from ${if (isUpper) "above" else "below"} to $bound ${bound.isRef(tparam)}") + if (bound.isRef(tparam)) false + else bound match { + case bound: TypeRef + if bound.symbol.is(BindDefinedType) && + ctx.gadt.bounds.contains(bound.symbol) && + !tr.symbol.is(BindDefinedType) => + // Avoid having pattern-bound types in gadt bounds, + // as these might be eliminated once the pattern is typechecked. + // Pattern-bound type symbols should be narrowed first, only if that fails + // should symbols in the environment be constrained. + narrowGADTBounds(bound, tr, !isUpper) + case _ => + val oldBounds = ctx.gadt.bounds(tparam) + val newBounds = + if (isUpper) TypeBounds(oldBounds.lo, oldBounds.hi & bound) + else TypeBounds(oldBounds.lo | bound, oldBounds.hi) + isSubType(newBounds.lo, newBounds.hi) && + { ctx.gadt.setBounds(tparam, newBounds); true } + } + } + + // Tests around `matches` + + /** A function implementing `tp1` matches `tp2`. */ + final def matchesType(tp1: Type, tp2: Type, relaxed: Boolean): Boolean = tp1.widen match { + case tp1: MethodType => + tp2.widen match { + case tp2: MethodType => + tp1.isImplicit == tp2.isImplicit && + matchingParams(tp1.paramTypes, tp2.paramTypes, tp1.isJava, tp2.isJava) && + matchesType(tp1.resultType, tp2.resultType.subst(tp2, tp1), relaxed) + case tp2 => + relaxed && tp1.paramNames.isEmpty && + matchesType(tp1.resultType, tp2, relaxed) + } + case tp1: PolyType => + tp2.widen match { + case tp2: PolyType => + sameLength(tp1.paramNames, tp2.paramNames) && + matchesType(tp1.resultType, tp2.resultType.subst(tp2, tp1), relaxed) + case _ => + false + } + case _ => + tp2.widen match { + case _: PolyType => + false + case tp2: MethodType => + relaxed && tp2.paramNames.isEmpty && + matchesType(tp1, tp2.resultType, relaxed) + case tp2 => + relaxed || isSameType(tp1, tp2) + } + } + + /** Are `syms1` and `syms2` parameter lists with pairwise equivalent types? */ + def matchingParams(formals1: List[Type], formals2: List[Type], isJava1: Boolean, isJava2: Boolean): Boolean = formals1 match { + case formal1 :: rest1 => + formals2 match { + case formal2 :: rest2 => + (isSameTypeWhenFrozen(formal1, formal2) + || isJava1 && (formal2 isRef ObjectClass) && (formal1 isRef AnyClass) + || isJava2 && (formal1 isRef ObjectClass) && (formal2 isRef AnyClass)) && + matchingParams(rest1, rest2, isJava1, isJava2) + case nil => + false + } + case nil => + formals2.isEmpty + } + + /** Do generic types `poly1` and `poly2` have type parameters that + * have the same bounds (after renaming one set to the other)? + */ + def matchingTypeParams(poly1: PolyType, poly2: PolyType): Boolean = + (poly1.paramBounds corresponds poly2.paramBounds)((b1, b2) => + isSameType(b1, b2.subst(poly2, poly1))) + + // Type equality =:= + + /** Two types are the same if are mutual subtypes of each other */ + def isSameType(tp1: Type, tp2: Type): Boolean = + if (tp1 eq NoType) false + else if (tp1 eq tp2) true + else isSubType(tp1, tp2) && isSubType(tp2, tp1) + + /** Same as `isSameType` but also can be applied to overloaded TermRefs, where + * two overloaded refs are the same if they have pairwise equal alternatives + */ + def isSameRef(tp1: Type, tp2: Type): Boolean = ctx.traceIndented(s"isSameRef($tp1, $tp2") { + def isSubRef(tp1: Type, tp2: Type): Boolean = tp1 match { + case tp1: TermRef if tp1.isOverloaded => + tp1.alternatives forall (isSubRef(_, tp2)) + case _ => + tp2 match { + case tp2: TermRef if tp2.isOverloaded => + tp2.alternatives exists (isSubRef(tp1, _)) + case _ => + isSubType(tp1, tp2) + } + } + isSubRef(tp1, tp2) && isSubRef(tp2, tp1) + } + + /** The greatest lower bound of two types */ + def glb(tp1: Type, tp2: Type): Type = /*>|>*/ ctx.traceIndented(s"glb(${tp1.show}, ${tp2.show})", subtyping, show = true) /*<|<*/ { + if (tp1 eq tp2) tp1 + else if (!tp1.exists) tp2 + else if (!tp2.exists) tp1 + else if ((tp1 isRef AnyClass) || (tp2 isRef NothingClass)) tp2 + else if ((tp2 isRef AnyClass) || (tp1 isRef NothingClass)) tp1 + else tp2 match { // normalize to disjunctive normal form if possible. + case OrType(tp21, tp22) => + tp1 & tp21 | tp1 & tp22 + case _ => + tp1 match { + case OrType(tp11, tp12) => + tp11 & tp2 | tp12 & tp2 + case _ => + val t1 = mergeIfSub(tp1, tp2) + if (t1.exists) t1 + else { + val t2 = mergeIfSub(tp2, tp1) + if (t2.exists) t2 + else tp1 match { + case tp1: ConstantType => + tp2 match { + case tp2: ConstantType => + // Make use of the fact that the intersection of two constant types + // types which are not subtypes of each other is known to be empty. + // Note: The same does not apply to singleton types in general. + // E.g. we could have a pattern match against `x.type & y.type` + // which might succeed if `x` and `y` happen to be the same ref + // at run time. It would not work to replace that with `Nothing`. + // However, maybe we can still apply the replacement to + // types which are not explicitly written. + defn.NothingType + case _ => andType(tp1, tp2) + } + case _ => andType(tp1, tp2) + } + } + } + } + } + + /** The greatest lower bound of a list types */ + final def glb(tps: List[Type]): Type = + ((defn.AnyType: Type) /: tps)(glb) + + /** The least upper bound of two types + * @note We do not admit singleton types in or-types as lubs. + */ + def lub(tp1: Type, tp2: Type): Type = /*>|>*/ ctx.traceIndented(s"lub(${tp1.show}, ${tp2.show})", subtyping, show = true) /*<|<*/ { + if (tp1 eq tp2) tp1 + else if (!tp1.exists) tp1 + else if (!tp2.exists) tp2 + else if ((tp1 isRef AnyClass) || (tp2 isRef NothingClass)) tp1 + else if ((tp2 isRef AnyClass) || (tp1 isRef NothingClass)) tp2 + else { + val t1 = mergeIfSuper(tp1, tp2) + if (t1.exists) t1 + else { + val t2 = mergeIfSuper(tp2, tp1) + if (t2.exists) t2 + else { + val tp1w = tp1.widen + val tp2w = tp2.widen + if ((tp1 ne tp1w) || (tp2 ne tp2w)) lub(tp1w, tp2w) + else orType(tp1w, tp2w) // no need to check subtypes again + } + } + } + } + + /** The least upper bound of a list of types */ + final def lub(tps: List[Type]): Type = + ((defn.NothingType: Type) /: tps)(lub) + + /** Merge `t1` into `tp2` if t1 is a subtype of some &-summand of tp2. + */ + private def mergeIfSub(tp1: Type, tp2: Type): Type = + if (isSubTypeWhenFrozen(tp1, tp2)) + if (isSubTypeWhenFrozen(tp2, tp1)) tp2 else tp1 // keep existing type if possible + else tp2 match { + case tp2 @ AndType(tp21, tp22) => + val lower1 = mergeIfSub(tp1, tp21) + if (lower1 eq tp21) tp2 + else if (lower1.exists) lower1 & tp22 + else { + val lower2 = mergeIfSub(tp1, tp22) + if (lower2 eq tp22) tp2 + else if (lower2.exists) tp21 & lower2 + else NoType + } + case _ => + NoType + } + + /** Merge `tp1` into `tp2` if tp1 is a supertype of some |-summand of tp2. + */ + private def mergeIfSuper(tp1: Type, tp2: Type): Type = + if (isSubTypeWhenFrozen(tp2, tp1)) + if (isSubTypeWhenFrozen(tp1, tp2)) tp2 else tp1 // keep existing type if possible + else tp2 match { + case tp2 @ OrType(tp21, tp22) => + val higher1 = mergeIfSuper(tp1, tp21) + if (higher1 eq tp21) tp2 + else if (higher1.exists) higher1 | tp22 + else { + val higher2 = mergeIfSuper(tp1, tp22) + if (higher2 eq tp22) tp2 + else if (higher2.exists) tp21 | higher2 + else NoType + } + case _ => + NoType + } + + /** Form a normalized conjunction of two types. + * Note: For certain types, `&` is distributed inside the type. This holds for + * all types which are not value types (e.g. TypeBounds, ClassInfo, + * ExprType, MethodType, PolyType). Also, when forming an `&`, + * instantiated TypeVars are dereferenced and annotations are stripped. + * Finally, refined types with the same refined name are + * opportunistically merged. + * + * Sometimes, the conjunction of two types cannot be formed because + * the types are in conflict of each other. In particular: + * + * 1. Two different class types are conflicting. + * 2. A class type conflicts with a type bounds that does not include the class reference. + * 3. Two method or poly types with different (type) parameters but the same + * signature are conflicting + * + * In these cases, a MergeError is thrown. + */ + final def andType(tp1: Type, tp2: Type, erased: Boolean = ctx.erasedTypes) = ctx.traceIndented(s"glb(${tp1.show}, ${tp2.show})", subtyping, show = true) { + val t1 = distributeAnd(tp1, tp2) + if (t1.exists) t1 + else { + val t2 = distributeAnd(tp2, tp1) + if (t2.exists) t2 + else if (erased) erasedGlb(tp1, tp2, isJava = false) + else liftIfHK(tp1, tp2, AndType(_, _), _ & _) + } + } + + /** Form a normalized conjunction of two types. + * Note: For certain types, `|` is distributed inside the type. This holds for + * all types which are not value types (e.g. TypeBounds, ClassInfo, + * ExprType, MethodType, PolyType). Also, when forming an `|`, + * instantiated TypeVars are dereferenced and annotations are stripped. + * + * Sometimes, the disjunction of two types cannot be formed because + * the types are in conflict of each other. (@see `andType` for an enumeration + * of these cases). In cases of conflict a `MergeError` is raised. + * + * @param erased Apply erasure semantics. If erased is true, instead of creating + * an OrType, the lub will be computed using TypeCreator#erasedLub. + */ + final def orType(tp1: Type, tp2: Type, erased: Boolean = ctx.erasedTypes) = { + val t1 = distributeOr(tp1, tp2) + if (t1.exists) t1 + else { + val t2 = distributeOr(tp2, tp1) + if (t2.exists) t2 + else if (erased) erasedLub(tp1, tp2) + else liftIfHK(tp1, tp2, OrType(_, _), _ | _) + } + } + + /** `op(tp1, tp2)` unless `tp1` and `tp2` are type-constructors with at least + * some unnamed type parameters. + * In the latter case, combine `tp1` and `tp2` under a type lambda like this: + * + * [X1, ..., Xn] -> op(tp1[X1, ..., Xn], tp2[X1, ..., Xn]) + * + * Note: There is a tension between named and positional parameters here, which + * is impossible to resolve completely. Say you have + * + * C[type T], D[type U] + * + * Then do you expand `C & D` to `[T] -> C[T] & D[T]` or not? Under the named + * type parameter interpretation, this would be wrong whereas under the traditional + * higher-kinded interpretation this would be required. The problem arises from + * allowing both interpretations. A possible remedy is to be somehow stricter + * in where we allow which interpretation. + */ + private def liftIfHK(tp1: Type, tp2: Type, op: (Type, Type) => Type, original: (Type, Type) => Type) = { + val tparams1 = tp1.typeParams + val tparams2 = tp2.typeParams + if (tparams1.isEmpty) + if (tparams2.isEmpty) op(tp1, tp2) + else original(tp1, tp2.appliedTo(tp2.typeParams.map(_.paramBoundsAsSeenFrom(tp2)))) + else if (tparams2.isEmpty) + original(tp1.appliedTo(tp1.typeParams.map(_.paramBoundsAsSeenFrom(tp1))), tp2) + else + PolyType( + paramNames = tpnme.syntheticTypeParamNames(tparams1.length), + variances = (tparams1, tparams2).zipped.map((tparam1, tparam2) => + (tparam1.paramVariance + tparam2.paramVariance) / 2))( + paramBoundsExp = tl => (tparams1, tparams2).zipped.map((tparam1, tparam2) => + tl.lifted(tparams1, tparam1.paramBoundsAsSeenFrom(tp1)).bounds & + tl.lifted(tparams2, tparam2.paramBoundsAsSeenFrom(tp2)).bounds), + resultTypeExp = tl => + original(tl.lifted(tparams1, tp1).appliedTo(tl.paramRefs), + tl.lifted(tparams2, tp2).appliedTo(tl.paramRefs))) + } + + /** Try to distribute `&` inside type, detect and handle conflicts + * @pre !(tp1 <: tp2) && !(tp2 <:< tp1) -- these cases were handled before + */ + private def distributeAnd(tp1: Type, tp2: Type): Type = tp1 match { + // opportunistically merge same-named refinements + // this does not change anything semantically (i.e. merging or not merging + // gives =:= types), but it keeps the type smaller. + case tp1: RefinedType => + tp2 match { + case tp2: RefinedType if tp1.refinedName == tp2.refinedName => + // Given two refinements `T1 { X = S1 }` and `T2 { X = S2 }`, if `S1 =:= S2` + // (possibly by instantiating type parameters), rewrite to `T1 & T2 { X = S1 }`. + // Otherwise rewrite to `T1 & T2 { X B }` where `B` is the conjunction of + // the bounds of `X` in `T1` and `T2`. + // The first rule above is contentious because it cuts the constraint set. + // But without it we would replace the two aliases by + // `T { X >: S1 | S2 <: S1 & S2 }`, which looks weird and is probably + // not what's intended. + val rinfo1 = tp1.refinedInfo + val rinfo2 = tp2.refinedInfo + val parent = tp1.parent & tp2.parent + val rinfo = + if (rinfo1.isAlias && rinfo2.isAlias && isSameType(rinfo1, rinfo2)) + rinfo1 + else + rinfo1 & rinfo2 + tp1.derivedRefinedType(parent, tp1.refinedName, rinfo) + case _ => + NoType + } + case tp1: RecType => + tp1.rebind(distributeAnd(tp1.parent, tp2)) + case ExprType(rt1) => + tp2 match { + case ExprType(rt2) => + ExprType(rt1 & rt2) + case _ => + rt1 & tp2 + } + case tp1: TypeVar if tp1.isInstantiated => + tp1.underlying & tp2 + case tp1: AnnotatedType => + tp1.underlying & tp2 + case _ => + NoType + } + + /** Try to distribute `|` inside type, detect and handle conflicts + * Note that, unlike for `&`, a disjunction cannot be pushed into + * a refined or applied type. Example: + * + * List[T] | List[U] is not the same as List[T | U]. + * + * The rhs is a proper supertype of the lhs. + */ + private def distributeOr(tp1: Type, tp2: Type): Type = tp1 match { + case ExprType(rt1) => + ExprType(rt1 | tp2.widenExpr) + case tp1: TypeVar if tp1.isInstantiated => + tp1.underlying | tp2 + case tp1: AnnotatedType => + tp1.underlying | tp2 + case _ => + NoType + } + + /** Show type, handling type types better than the default */ + private def showType(tp: Type)(implicit ctx: Context) = tp match { + case ClassInfo(_, cls, _, _, _) => cls.showLocated + case bounds: TypeBounds => "type bounds" + bounds.show + case _ => tp.show + } + + /** A comparison function to pick a winner in case of a merge conflict */ + private def isAsGood(tp1: Type, tp2: Type): Boolean = tp1 match { + case tp1: ClassInfo => + tp2 match { + case tp2: ClassInfo => + isSubTypeWhenFrozen(tp1.prefix, tp2.prefix) || (tp1.cls.owner derivesFrom tp2.cls.owner) + case _ => + false + } + case tp1: PolyType => + tp2 match { + case tp2: PolyType => + tp1.typeParams.length == tp2.typeParams.length && + isAsGood(tp1.resultType, tp2.resultType.subst(tp2, tp1)) + case _ => + false + } + case tp1: MethodType => + tp2 match { + case tp2: MethodType => + def asGoodParams(formals1: List[Type], formals2: List[Type]) = + (formals2 corresponds formals1)(isSubTypeWhenFrozen) + asGoodParams(tp1.paramTypes, tp2.paramTypes) && + (!asGoodParams(tp2.paramTypes, tp1.paramTypes) || + isAsGood(tp1.resultType, tp2.resultType)) + case _ => + false + } + case _ => + false + } + + /** A new type comparer of the same type as this one, using the given context. */ + def copyIn(ctx: Context) = new TypeComparer(ctx) + + // ----------- Diagnostics -------------------------------------------------- + + /** A hook for showing subtype traces. Overridden in ExplainingTypeComparer */ + def traceIndented[T](str: String)(op: => T): T = op + + private def traceInfo(tp1: Type, tp2: Type) = + s"${tp1.show} <:< ${tp2.show}" + { + if (ctx.settings.verbose.value || Config.verboseExplainSubtype) { + s" ${tp1.getClass}, ${tp2.getClass}" + + (if (frozenConstraint) " frozen" else "") + + (if (ctx.mode is Mode.TypevarsMissContext) " tvars-miss-ctx" else "") + } + else "" + } + + /** Show subtype goal that led to an assertion failure */ + def showGoal(tp1: Type, tp2: Type)(implicit ctx: Context) = { + println(ex"assertion failure for $tp1 <:< $tp2, frozen = $frozenConstraint") + def explainPoly(tp: Type) = tp match { + case tp: PolyParam => ctx.echo(s"polyparam ${tp.show} found in ${tp.binder.show}") + case tp: TypeRef if tp.symbol.exists => ctx.echo(s"typeref ${tp.show} found in ${tp.symbol.owner.show}") + case tp: TypeVar => ctx.echo(s"typevar ${tp.show}, origin = ${tp.origin}") + case _ => ctx.echo(s"${tp.show} is a ${tp.getClass}") + } + explainPoly(tp1) + explainPoly(tp2) + } + + /** Record statistics about the total number of subtype checks + * and the number of "successful" subtype checks, i.e. checks + * that form part of a subtype derivation tree that's ultimately successful. + */ + def recordStatistics(result: Boolean, prevSuccessCount: Int) = { + // Stats.record(s"isSubType ${tp1.show} <:< ${tp2.show}") + totalCount += 1 + if (result) successCount += 1 else successCount = prevSuccessCount + if (recCount == 0) { + Stats.record("successful subType", successCount) + Stats.record("total subType", totalCount) + successCount = 0 + totalCount = 0 + } + } +} + +object TypeComparer { + + /** Show trace of comparison operations when performing `op` as result string */ + def explained[T](op: Context => T)(implicit ctx: Context): String = { + val nestedCtx = ctx.fresh.setTypeComparerFn(new ExplainingTypeComparer(_)) + op(nestedCtx) + nestedCtx.typeComparer.toString + } +} + +/** A type comparer that can record traces of subtype operations */ +class ExplainingTypeComparer(initctx: Context) extends TypeComparer(initctx) { + private var indent = 0 + private val b = new StringBuilder + + private var skipped = false + + override def traceIndented[T](str: String)(op: => T): T = + if (skipped) op + else { + indent += 2 + b append "\n" append (" " * indent) append "==> " append str + val res = op + b append "\n" append (" " * indent) append "<== " append str append " = " append show(res) + indent -= 2 + res + } + + private def show(res: Any) = res match { + case res: printing.Showable if !ctx.settings.Yexplainlowlevel.value => res.show + case _ => String.valueOf(res) + } + + override def isSubType(tp1: Type, tp2: Type) = + traceIndented(s"${show(tp1)} <:< ${show(tp2)}${if (Config.verboseExplainSubtype) s" ${tp1.getClass} ${tp2.getClass}" else ""}${if (frozenConstraint) " frozen" else ""}") { + super.isSubType(tp1, tp2) + } + + override def hasMatchingMember(name: Name, tp1: Type, tp2: RefinedType): Boolean = + traceIndented(s"hasMatchingMember(${show(tp1)} . $name, ${show(tp2.refinedInfo)}), member = ${show(tp1.member(name).info)}") { + super.hasMatchingMember(name, tp1, tp2) + } + + override def lub(tp1: Type, tp2: Type) = + traceIndented(s"lub(${show(tp1)}, ${show(tp2)})") { + super.lub(tp1, tp2) + } + + override def glb(tp1: Type, tp2: Type) = + traceIndented(s"glb(${show(tp1)}, ${show(tp2)})") { + super.glb(tp1, tp2) + } + + override def addConstraint(param: PolyParam, bound: Type, fromBelow: Boolean): Boolean = + traceIndented(i"add constraint $param ${if (fromBelow) ">:" else "<:"} $bound $frozenConstraint, constraint = ${ctx.typerState.constraint}") { + super.addConstraint(param, bound, fromBelow) + } + + override def copyIn(ctx: Context) = new ExplainingTypeComparer(ctx) + + override def compareHkApply2(tp1: Type, tp2: HKApply, tycon2: Type, args2: List[Type]): Boolean = { + def addendum = "" + traceIndented(i"compareHkApply $tp1, $tp2$addendum") { + super.compareHkApply2(tp1, tp2, tycon2, args2) + } + } + + override def toString = "Subtype trace:" + { try b.toString finally b.clear() } +} |