From 7c40b03aa9210dc1989cc08c0f03f7d4dd4e9e47 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 21 Dec 2014 12:59:28 +0100 Subject: [PATCH 01/72] test reorg Moved working tests to pos, annotated non-working ones. --- tests/pending/pos/channels.scala | 4 +++- tests/{pending => }/pos/bounds.scala | 0 tests/{pending => }/pos/caseClassInMethod.scala | 0 .../{pending => }/pos/class-dependent-extension-method.scala | 0 4 files changed, 3 insertions(+), 1 deletion(-) rename tests/{pending => }/pos/bounds.scala (100%) rename tests/{pending => }/pos/caseClassInMethod.scala (100%) rename tests/{pending => }/pos/class-dependent-extension-method.scala (100%) diff --git a/tests/pending/pos/channels.scala b/tests/pending/pos/channels.scala index b2f0cdc32135..77736305f474 100644 --- a/tests/pending/pos/channels.scala +++ b/tests/pending/pos/channels.scala @@ -1,3 +1,5 @@ +// To compile this test, we need some more elaborate GADT capabilities. +// Not sure yet we should invest to get them. class Channel[a] import collection.mutable.Set @@ -16,7 +18,7 @@ object Test extends App { def f[b](x: ![b]): Int = x match { case send: ![c] => send.chan match { - case IC => send.data + case IC => send.data // Here, from the fact that `chan` is an IC, we need to conclude that `c` is Int. } } } diff --git a/tests/pending/pos/bounds.scala b/tests/pos/bounds.scala similarity index 100% rename from tests/pending/pos/bounds.scala rename to tests/pos/bounds.scala diff --git a/tests/pending/pos/caseClassInMethod.scala b/tests/pos/caseClassInMethod.scala similarity index 100% rename from tests/pending/pos/caseClassInMethod.scala rename to tests/pos/caseClassInMethod.scala diff --git a/tests/pending/pos/class-dependent-extension-method.scala b/tests/pos/class-dependent-extension-method.scala similarity index 100% rename from tests/pending/pos/class-dependent-extension-method.scala rename to tests/pos/class-dependent-extension-method.scala From 9d8e473739ed4f5618d55738475717872453018a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 22 Dec 2014 11:01:52 +0100 Subject: [PATCH 02/72] Handle subtyping cases involving null. Cases handled are: Null <: (A & B) { ... } Null <: (A | B) { ... } --- src/dotty/tools/dotc/core/TypeComparer.scala | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index cae4500e465b..7eb1af1279f5 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -661,11 +661,11 @@ class TypeComparer(initctx: Context) extends DotClass { } compareNamed case tp2 @ RefinedType(parent2, name2) => - def qualifies(m: SingleDenotation) = isSubType(m.info, tp2.refinedInfo) - def memberMatches(mbr: Denotation): Boolean = mbr match { // inlined hasAltWith for performance - case mbr: SingleDenotation => qualifies(mbr) - case _ => mbr hasAltWith qualifies - } + def qualifies(m: SingleDenotation) = isSubType(m.info, tp2.refinedInfo) + def memberMatches(mbr: Denotation): Boolean = mbr match { // inlined hasAltWith for performance + case mbr: SingleDenotation => qualifies(mbr) + case _ => mbr hasAltWith qualifies + } def compareRefinedSlow: Boolean = { def hasMatchingMember(name: Name): Boolean = /*>|>*/ ctx.traceIndented(s"hasMatchingMember($name) ${tp1.member(name).info.show}", subtyping) /*<|<*/ { val tp1r = rebaseQual(tp1, name) @@ -786,7 +786,9 @@ class TypeComparer(initctx: Context) extends DotClass { def isNullable(tp: Type): Boolean = tp.dealias match { case tp: TypeRef => tp.symbol.isNullableClass case RefinedType(parent, _) => isNullable(parent) - case _ => false + case AndType(tp1, tp2) => isNullable(tp1) && isNullable(tp2) + case OrType(tp1, tp2) => isNullable(tp1) || isNullable(tp2) + case _ => println(i"$tp of class ${tp.getClass} is not nullable"); false } (tp1.symbol eq NothingClass) && tp2.isInstanceOf[ValueType] || (tp1.symbol eq NullClass) && isNullable(tp2) From e50f47c17eea3aee80db2d86e28e7ae016f94cbb Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 31 Dec 2014 17:23:21 +0100 Subject: [PATCH 03/72] Better name for pattern --- src/dotty/tools/dotc/printing/RefinedPrinter.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/src/dotty/tools/dotc/printing/RefinedPrinter.scala index b55f8e8e01f5..babf4250330e 100644 --- a/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -112,7 +112,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { val hideType = tp.symbol is TypeParam | TypeArgument | ExpandedName if (hideType && !ctx.phase.erasedTypes && !tp.symbol.isCompleting) { tp.info match { - case TypeAlias(hi) => return toText(hi) + case TypeAlias(alias) => return toText(alias) case _ => if (tp.prefix.isInstanceOf[ThisType]) return nameString(tp.symbol) } } From e3a43806a2b5b17982e942a82cabe139c09d971e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 1 Jan 2015 13:45:08 +0100 Subject: [PATCH 04/72] Reorg of subtyping. Plus, RefinedThis gets a second parameter, `level`. This will replace the first one in due time. --- src/dotty/tools/dotc/core/Substituters.scala | 2 +- src/dotty/tools/dotc/core/TypeComparer.scala | 878 +++++++++--------- src/dotty/tools/dotc/core/Types.scala | 42 +- .../tools/dotc/printing/PlainPrinter.scala | 2 +- tests/pending/pos/compound.scala | 4 + tests/pending/pos/subtypcycle.scala | 10 + tests/pos/Patterns.scala | 2 +- tests/pos/refinedSubtyping.scala | 11 + 8 files changed, 505 insertions(+), 446 deletions(-) create mode 100644 tests/pending/pos/subtypcycle.scala diff --git a/src/dotty/tools/dotc/core/Substituters.scala b/src/dotty/tools/dotc/core/Substituters.scala index 02810733ad45..2051885075d7 100644 --- a/src/dotty/tools/dotc/core/Substituters.scala +++ b/src/dotty/tools/dotc/core/Substituters.scala @@ -181,7 +181,7 @@ trait Substituters { this: Context => final def substThis(tp: Type, from: RefinedType, to: Type, theMap: SubstRefinedThisMap): Type = tp match { - case tp @ RefinedThis(rt) => + case tp @ RefinedThis(rt, _) => if (rt eq from) to else tp case tp: NamedType => if (tp.currentSymbol.isStatic) tp diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index 7eb1af1279f5..b8cc45fa7a9c 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -328,7 +328,7 @@ class TypeComparer(initctx: Context) extends DotClass { } } tp.prefix match { - case RefinedThis(rt) => rebaseFrom(rt) + case RefinedThis(rt, _) => rebaseFrom(rt) case pre: ThisType => rebaseFrom(pre.cls.info) case _ => tp } @@ -357,317 +357,247 @@ class TypeComparer(initctx: Context) extends DotClass { (tp2 eq AnyType) && tp1.isValueType) return true isSubType(tp1, tp2) } - - def isSubTypeWhenFrozen(tp1: Type, tp2: Type): Boolean = { + + protected def isSubTypeWhenFrozen(tp1: Type, tp2: Type): Boolean = isSubTypeWhenFrozen(tp1, tp2, tp1) + protected def isSubTypeWhenFrozen(tp1: Type, tp2: Type, pre: Type): Boolean = { val saved = frozenConstraint frozenConstraint = true - try isSubType(tp1, tp2) + try isSubType(tp1, tp2, pre) finally frozenConstraint = saved } - def isNonBottomSubType(tp1: Type, tp2: Type): Boolean = - !(tp2 isRef NothingClass) && isSubType(tp1, tp2) - private def traceInfo(tp1: Type, tp2: Type) = s"${tp1.show} <:< ${tp2.show}" + (if (ctx.settings.verbose.value) s" ${tp1.getClass} ${tp2.getClass}${if (frozenConstraint) " frozen" else ""}" else "") + + def isSubType(tp1: Type, tp2: Type): Boolean = isSubType(tp1, tp2, tp1) + def isSubType(tp1: Type, tp2: Type, pre: Type): Boolean = /*>|>*/ ctx.traceIndented(s"isSubType ${traceInfo(tp1, tp2)}", subtyping) /*<|<*/ { - 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 < LogPendingSubTypesThreshold) firstTry(tp1, tp2) - else monitoredIsSubType(tp1, tp2) - recCount = recCount - 1 - if (!result) constraint = saved - else if (recCount == 0 && needsGc) state.gc() - - def recordStatistics = { - // Stats.record(s"isSubType ${tp1.show} <:< ${tp2.show}") - totalCount += 1 - if (result) successCount += 1 else successCount = savedSuccessCount - if (recCount == 0) { - Stats.record("successful subType", successCount) - Stats.record("total subType", totalCount) - successCount = 0 - totalCount = 0 + def firstTry(tp1: Type, tp2: Type): Boolean = { + tp2 match { + case tp2: NamedType => + def isHKSubType = tp2.name == tpnme.Apply && { + val lambda2 = tp2.prefix.LambdaClass(forcing = true) + lambda2.exists && !tp1.isLambda && + tp1.testLifted(lambda2.typeParams, isSubType(_, tp2.prefix)) } - } - if (Stats.monitored) recordStatistics - - result - } catch { - case NonFatal(ex) => - def showState = { - println(disambiguated(implicit ctx => s"assertion failure for ${tp1.show} <:< ${tp2.show}, frozen = $frozenConstraint")) - def explainPoly(tp: Type) = tp match { - case tp: PolyParam => println(s"polyparam ${tp.show} found in ${tp.binder.show}") - case tp: TypeRef if tp.symbol.exists => println(s"typeref ${tp.show} found in ${tp.symbol.owner.show}") - case tp: TypeVar => println(s"typevar ${tp.show}, origin = ${tp.origin}") - case _ => println(s"${tp.show} is a ${tp.getClass}") + def compareNamed = { + implicit val ctx: Context = this.ctx // Dotty deviation: implicits need explicit type + tp1 match { + case tp1: NamedType => + val sym1 = tp1.symbol + (if ((sym1 ne NoSymbol) && (sym1 eq tp2.symbol)) ( + ctx.erasedTypes + || sym1.isStaticOwner + || { // Implements: A # X <: B # X + // if either A =:= B (i.e. A <: B and B <: A), or the following three conditions hold: + // 1. X is a class type, + // 2. B is a class type without abstract type members. + // 3. A <: B. + // Dealiasing is taken care of elsewhere. + val pre1 = tp1.prefix + val pre2 = tp2.prefix + ( isSameType(pre1, pre2) + || sym1.isClass + && pre2.classSymbol.exists + && pre2.abstractTypeMembers.isEmpty + && isSubType(pre1, pre2) + ) + } + ) + else (tp1.name eq tp2.name) && isSameType(tp1.prefix, tp2.prefix) + ) || isHKSubType || secondTryNamed(tp1, tp2) + case tp1: ThisType if tp1.cls eq tp2.symbol.moduleClass => + isSubType(tp1.cls.owner.thisType, tp2.prefix) + case _ => + isHKSubType || secondTry(tp1, tp2) } - explainPoly(tp1) - explainPoly(tp2) } - if (ex.isInstanceOf[AssertionError]) showState - recCount -= 1 - constraint = saved - successCount = savedSuccessCount - throw ex - } - } - } - - 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}") - 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 - } - } - } - - def firstTry(tp1: Type, tp2: Type): Boolean = { - tp2 match { - case tp2: NamedType => - def isHKSubType = tp2.name == tpnme.Apply && { - val lambda2 = tp2.prefix.LambdaClass(forcing = true) - lambda2.exists && !tp1.isLambda && - tp1.testLifted(lambda2.typeParams, isSubType(_, tp2.prefix)) - } - def compareNamed = { - implicit val ctx: Context = this.ctx // Dotty deviation: implicits need explicit type + compareNamed + case tp2: ProtoType => + isMatchedByProto(tp2, tp1) + case tp2: PolyParam => + def comparePolyParam = + tp2 == tp1 || { + if (solvedConstraint && (constraint contains tp2)) isSubType(tp1, bounds(tp2).lo, pre) + else + isSubTypeWhenFrozen(tp1, bounds(tp2).lo) || { + if (isConstrained(tp2)) addConstraint(tp2, tp1.widenExpr, fromBelow = true) + else (ctx.mode is Mode.TypevarsMissContext) || secondTry(tp1, tp2) + } + } + comparePolyParam + case tp2: BoundType => + tp2 == tp1 || secondTry(tp1, tp2) + case tp2: TypeVar => + isSubType(tp1, tp2.underlying, pre) + case tp2: WildcardType => + def compareWild = tp2.optBounds match { + case TypeBounds(_, hi) => isSubType(tp1, hi, pre) + case NoType => true + } + compareWild + case tp2: LazyRef => + isSubType(tp1, tp2.ref, pre) + case tp2: AnnotatedType => + isSubType(tp1, tp2.tpe, pre) // todo: refine? + case tp2: ThisType => tp1 match { - case tp1: NamedType => - val sym1 = tp1.symbol - (if ((sym1 ne NoSymbol) && (sym1 eq tp2.symbol)) ( - ctx.erasedTypes - || sym1.isStaticOwner - || { // Implements: A # X <: B # X - // if either A =:= B (i.e. A <: B and B <: A), or the following three conditions hold: - // 1. X is a class type, - // 2. B is a class type without abstract type members. - // 3. A <: B. - // Dealiasing is taken care of elsewhere. - val pre1 = tp1.prefix - val pre2 = tp2.prefix - ( isSameType(pre1, pre2) - || sym1.isClass - && pre2.classSymbol.exists - && pre2.abstractTypeMembers.isEmpty - && isSubType(pre1, pre2) - ) - } - ) - else (tp1.name eq tp2.name) && isSameType(tp1.prefix, tp2.prefix) - ) || isHKSubType || secondTryNamed(tp1, tp2) - case tp1: ThisType if tp1.cls eq tp2.symbol.moduleClass => - isSubType(tp1.cls.owner.thisType, tp2.prefix) + 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. + tp1.cls.classInfo.selfType.derivesFrom(tp2.cls) && + tp2.cls.classInfo.selfType.derivesFrom(tp1.cls) case _ => - isHKSubType || secondTry(tp1, tp2) + secondTry(tp1, tp2) } + case tp2: SuperType => + tp1 match { + case tp1: SuperType => + isSubType(tp1.thistpe, tp2.thistpe, pre) && + isSameType(tp1.supertpe, tp2.supertpe) + case _ => + secondTry(tp1, tp2) + } + case AndType(tp21, tp22) => + isSubType(tp1, tp21, pre) && isSubType(tp1, tp22, pre) + case ErrorType => + true + case _ => + secondTry(tp1, tp2) + } + } + + def secondTry(tp1: Type, tp2: Type): Boolean = tp1 match { + case tp1: NamedType => + tp2 match { + case tp2: ThisType if tp2.cls eq tp1.symbol.moduleClass => + isSubType(tp1.prefix, tp2.cls.owner.thisType) + case _ => + secondTryNamed(tp1, tp2) } - compareNamed - case tp2: ProtoType => - isMatchedByProto(tp2, tp1) - case tp2: PolyParam => + case OrType(tp11, tp12) => + isSubType(tp11, tp2, pre) && isSubType(tp12, tp2, pre) + case tp1: PolyParam => def comparePolyParam = - tp2 == tp1 || { - if (solvedConstraint && (constraint contains tp2)) isSubType(tp1, bounds(tp2).lo) + tp1 == tp2 || { + if (solvedConstraint && (constraint contains tp1)) isSubType(bounds(tp1).lo, tp2, pre) else - isSubTypeWhenFrozen(tp1, bounds(tp2).lo) || { - if (isConstrained(tp2)) addConstraint(tp2, tp1.widenExpr, fromBelow = true) - else (ctx.mode is Mode.TypevarsMissContext) || secondTry(tp1, tp2) + isSubTypeWhenFrozen(bounds(tp1).hi, tp2, pre) || { + if (isConstrained(tp1)) + addConstraint(tp1, tp2, fromBelow = false) && { + if ((!frozenConstraint) && + (tp2 isRef defn.NothingClass) && + state.isGlobalCommittable) { + def msg = s"!!! instantiated to Nothing: $tp1, constraint = ${constraint.show}" + if (Config.flagInstantiationToNothing) assert(false, msg) + else ctx.log(msg) + } + true + } + else (ctx.mode is Mode.TypevarsMissContext) || thirdTry(tp1, tp2) } } comparePolyParam - 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 => - isSubType(tp1, tp2.ref) - case tp2: AnnotatedType => - isSubType(tp1, tp2.tpe) // todo: refine? - case tp2: ThisType => - 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. - tp1.cls.classInfo.selfType.derivesFrom(tp2.cls) && - tp2.cls.classInfo.selfType.derivesFrom(tp1.cls) - case _ => - secondTry(tp1, tp2) + case tp1: RefinedThis => + tp2 match { + case tp2: RefinedThis if tp1.binder.parent =:= tp2.binder.parent => true + case _ => thirdTry(tp1, tp2) } - case tp2: SuperType => - tp1 match { - case tp1: SuperType => - isSubType(tp1.thistpe, tp2.thistpe) && - isSameType(tp1.supertpe, tp2.supertpe) - case _ => - secondTry(tp1, tp2) + case tp1: BoundType => + tp1 == tp2 || thirdTry(tp1, tp2) + case tp1: TypeVar => + (tp1 eq tp2) || isSubType(tp1.underlying, tp2, pre) + case tp1: WildcardType => + def compareWild = tp1.optBounds match { + case TypeBounds(lo, _) => isSubType(lo, tp2, pre) + case _ => true } - case AndType(tp21, tp22) => - isSubType(tp1, tp21) && isSubType(tp1, tp22) + compareWild + case tp1: LazyRef => + isSubType(tp1.ref, tp2, pre) + case tp1: AnnotatedType => + isSubType(tp1.tpe, tp2, pre) case ErrorType => true case _ => - secondTry(tp1, tp2) + thirdTry(tp1, tp2) } - } - - def secondTry(tp1: Type, tp2: Type): Boolean = tp1 match { - case tp1: NamedType => - tp2 match { - case tp2: ThisType if tp2.cls eq tp1.symbol.moduleClass => - isSubType(tp1.prefix, tp2.cls.owner.thisType) - case _ => - secondTryNamed(tp1, tp2) - } - case OrType(tp11, tp12) => - isSubType(tp11, tp2) && isSubType(tp12, tp2) - case tp1: PolyParam => - def comparePolyParam = - tp1 == tp2 || { - if (solvedConstraint && (constraint contains tp1)) isSubType(bounds(tp1).lo, tp2) - else - isSubTypeWhenFrozen(bounds(tp1).hi, tp2) || { - if (isConstrained(tp1)) - addConstraint(tp1, tp2, fromBelow = false) && { - if ((!frozenConstraint) && - (tp2 isRef defn.NothingClass) && - state.isGlobalCommittable) { - def msg = s"!!! instantiated to Nothing: $tp1, constraint = ${constraint.show}" - if (Config.flagInstantiationToNothing) assert(false, msg) - else ctx.log(msg) - } - true - } - else (ctx.mode is Mode.TypevarsMissContext) || thirdTry(tp1, tp2) - } - } - comparePolyParam - case tp1: RefinedThis => - tp2 match { - case tp2: RefinedThis if tp1.binder.parent =:= tp2.binder.parent => true - case _ => thirdTry(tp1, tp2) - } - case tp1: BoundType => - tp1 == tp2 || thirdTry(tp1, tp2) - case tp1: TypeVar => - (tp1 eq tp2) || isSubType(tp1.underlying, tp2) - case tp1: WildcardType => - def compareWild = tp1.optBounds match { - case TypeBounds(lo, _) => isSubType(lo, tp2) - case _ => true - } - compareWild - case tp1: LazyRef => - isSubType(tp1.ref, tp2) - case tp1: AnnotatedType => - isSubType(tp1.tpe, tp2) - case ErrorType => - true - case _ => - thirdTry(tp1, tp2) - } - def secondTryNamed(tp1: NamedType, tp2: Type): Boolean = { - def tryRebase2nd = { - val tp1rebased = rebase(tp1) - if (tp1rebased ne tp1) isSubType(tp1rebased, tp2) - else thirdTry(tp1, tp2) - } - tp1.info match { - // There was the following code, which was meant to implement this logic: - // If x has type A | B, then x.type <: C if - // x.type <: C assuming x has type A, and - // x.type <: C assuming x has type B. - // But it did not work, because derivedRef would always give back the same - // type and cache the denotation. So it ended up copmparing just one branch. - // The code seems to be unncessary for the tests and does not seems to help performance. - // So it is commented out. If we ever need to come back to this, we would have - // to create unchached TermRefs in order to avoid cross talk between the branches. - /* + def secondTryNamed(tp1: NamedType, tp2: Type): Boolean = { + def tryRebase2nd = { + val tp1rebased = rebase(tp1) + if (tp1rebased ne tp1) isSubType(tp1rebased, tp2) + else thirdTry(tp1, tp2) + } + tp1.info match { + // There was the following code, which was meant to implement this logic: + // If x has type A | B, then x.type <: C if + // x.type <: C assuming x has type A, and + // x.type <: C assuming x has type B. + // But it did not work, because derivedRef would always give back the same + // type and cache the denotation. So it ended up copmparing just one branch. + // The code seems to be unncessary for the tests and does not seems to help performance. + // So it is commented out. If we ever need to come back to this, we would have + // to create unchached TermRefs in order to avoid cross talk between the branches. + /* case OrType(tp11, tp12) => val sd = tp1.denot.asSingleDenotation def derivedRef(tp: Type) = NamedType(tp1.prefix, tp1.name, sd.derivedSingleDenotation(sd.symbol, tp)) secondTry(OrType.make(derivedRef(tp11), derivedRef(tp12)), tp2) */ - case TypeBounds(lo1, hi1) => - val gbounds1 = ctx.gadt.bounds(tp1.symbol) - if (gbounds1 != null) - isSubTypeWhenFrozen(gbounds1.hi, tp2) || - (ctx.mode is Mode.GADTflexible) && - narrowGADTBounds(tp1, TypeBounds(gbounds1.lo, gbounds1.hi & tp2)) || + case TypeBounds(lo1, hi1) => + val gbounds1 = ctx.gadt.bounds(tp1.symbol) + if (gbounds1 != null) + isSubTypeWhenFrozen(gbounds1.hi, tp2, pre) || + (ctx.mode is Mode.GADTflexible) && + narrowGADTBounds(tp1, TypeBounds(gbounds1.lo, gbounds1.hi & tp2)) || tryRebase2nd - else if (lo1 eq hi1) isSubType(hi1, tp2) + else if (lo1 eq hi1) isSubType(hi1, tp2, pre) else tryRebase2nd - case _ => + case _ => tryRebase2nd + } } - } - def thirdTry(tp1: Type, tp2: Type): Boolean = tp2 match { - case tp2: NamedType => - def tryRebase3rd = { - val tp2rebased = rebase(tp2) - if (tp2rebased ne tp2) isSubType(tp1, tp2rebased) - else fourthTry(tp1, tp2) - } - def compareNamed: Boolean = tp2.info match { - case TypeBounds(lo2, hi2) => - val gbounds2 = ctx.gadt.bounds(tp2.symbol) - if (gbounds2 != null) - isSubTypeWhenFrozen(tp1, gbounds2.lo) || - (ctx.mode is Mode.GADTflexible) && - narrowGADTBounds(tp2, TypeBounds(gbounds2.lo | tp1, gbounds2.hi)) || - tryRebase3rd - else - ((frozenConstraint || !isCappable(tp1)) && isSubType(tp1, lo2) - || tryRebase3rd) + def thirdTry(tp1: Type, tp2: Type): Boolean = tp2 match { + case tp2: NamedType => + def tryRebase3rd = { + val tp2rebased = rebase(tp2) + if (tp2rebased ne tp2) isSubType(tp1, tp2rebased) + else fourthTry(tp1, tp2) + } + def compareNamed: Boolean = tp2.info match { + case TypeBounds(lo2, hi2) => + val gbounds2 = ctx.gadt.bounds(tp2.symbol) + if (gbounds2 != null) + isSubTypeWhenFrozen(tp1, gbounds2.lo, pre) || + (ctx.mode is Mode.GADTflexible) && + narrowGADTBounds(tp2, TypeBounds(gbounds2.lo | tp1, gbounds2.hi)) || + fourthTry(tp1, tp2) + else + ((frozenConstraint || !isCappable(tp1)) && isSubType(tp1, lo2, pre) + || tryRebase3rd) - 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 - } + case _ => + val cls2 = tp2.symbol + if (cls2.isClass) { + val base = tp1.baseTypeRef(cls2) + if (base.exists && (base ne tp1)) return isSubType(base, tp2, pre) + if (cls2 == defn.SingletonClass && tp1.isStable) return true + } tryRebase3rd - } - compareNamed - case tp2 @ RefinedType(parent2, name2) => - def qualifies(m: SingleDenotation) = isSubType(m.info, tp2.refinedInfo) - def memberMatches(mbr: Denotation): Boolean = mbr match { // inlined hasAltWith for performance - case mbr: SingleDenotation => qualifies(mbr) - case _ => mbr hasAltWith qualifies - } - def compareRefinedSlow: Boolean = { - def hasMatchingMember(name: Name): Boolean = /*>|>*/ ctx.traceIndented(s"hasMatchingMember($name) ${tp1.member(name).info.show}", subtyping) /*<|<*/ { + } + compareNamed + case tp2 @ RefinedType(parent2, name2) => + def qualifies(m: SingleDenotation) = isSubType(m.info, tp2.refinedInfo) + def memberMatches(mbr: Denotation): Boolean = mbr match { // inlined hasAltWith for performance + case mbr: SingleDenotation => qualifies(mbr) + case _ => mbr hasAltWith qualifies + } + def compareRefinedSlow: Boolean = { + def hasMatchingMember(name: Name): Boolean = /*>|>*/ ctx.traceIndented(s"hasMatchingMember($name) ${tp1.member(name).info.show}", subtyping) /*<|<*/ { val tp1r = rebaseQual(tp1, name) (memberMatches(narrowRefined(tp1r) member name) || @@ -684,196 +614,266 @@ class TypeComparer(initctx: Context) extends DotClass { val saved = pendingRefinedBases try { addPendingName(name2, tp2, tp2) - isSubType(tp1, parent2) + isSubType(tp1, parent2, pre) } finally pendingRefinedBases = saved } (matchesParent && ( - name2 == nme.WILDCARD - || hasMatchingMember(name2) - || fourthTry(tp1, tp2)) - || needsEtaLift(tp1, tp2) && tp1.testLifted(tp2.typeParams, isSubType(_, tp2))) - } - def compareRefined: Boolean = tp1.widen match { - case tp1 @ RefinedType(parent1, name1) if name1 == name2 && name1.isTypeName => - normalizedInfo(tp1) match { - case bounds1 @ TypeBounds(lo1, hi1) if lo1 eq hi1 => - isSubType(bounds1, tp2.refinedInfo) && { + name2 == nme.WILDCARD + || hasMatchingMember(name2) + || fourthTry(tp1, tp2)) + || needsEtaLift(tp1, tp2) && tp1.testLifted(tp2.typeParams, isSubType(_, tp2, pre))) + } + def compareRefined: Boolean = tp1.widen match { + case tp1 @ RefinedType(parent1, name1) if name1 == name2 && name1.isTypeName => + normalizedInfo(tp1) match { + case bounds1: TypeAlias => + isSubType(bounds1, tp2.refinedInfo) && { val saved = pendingRefinedBases try { addPendingName(name1, tp1, tp2) - isSubType(parent1, parent2) + isSubType(parent1, parent2, pre) } finally pendingRefinedBases = saved } - case _ => - compareRefinedSlow - } - case _ => - compareRefinedSlow - } - compareRefined - case OrType(tp21, tp22) => - eitherIsSubType(tp1, tp21, tp1, tp22) || fourthTry(tp1, tp2) - case tp2 @ MethodType(_, formals2) => - def compareMethod = tp1 match { - case tp1 @ MethodType(_, formals1) => - (tp1.signature sameParams tp2.signature) && - (if (Config.newMatch) subsumeParams(formals1, formals2, tp1.isJava, tp2.isJava) - else 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: PolyType => - def comparePoly = tp1 match { - case tp1: PolyType => - (tp1.signature sameParams tp2.signature) && - matchingTypeParams(tp1, tp2) && - isSubType(tp1.resultType, tp2.resultType.subst(tp2, tp1)) - case _ => - false - } - comparePoly - 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 || isSubType(lo2, lo1)) && - (tp2.variance < 0 && tp1.variance <= 0 || isSubType(hi1, hi2)) - case tp1: ClassInfo => - val tt = tp1.typeRef - isSubType(lo2, tt) && isSubType(tt, hi2) - case _ => - false - } - compareTypeBounds - case ClassInfo(pre2, cls2, _, _, _) => - def compareClassInfo = tp1 match { - case ClassInfo(pre1, cls1, _, _, _) => - (cls1 eq cls2) && isSubType(pre2, pre1) - case _ => - false - } - compareClassInfo - case JavaArrayType(elem2) => - def compareJavaArray = tp1 match { - case JavaArrayType(elem1) => isSubType(elem1, elem2) - case _ => fourthTry(tp1, tp2) - } - compareJavaArray - case _ => - fourthTry(tp1, tp2) - } - - def fourthTry(tp1: Type, tp2: Type): Boolean = tp1 match { - case tp1: TypeRef => - tp1.info match { - case TypeBounds(lo1, hi1) => - isSubType(hi1, tp2) - case _ => - def isNullable(tp: Type): Boolean = tp.dealias match { - case tp: TypeRef => tp.symbol.isNullableClass - case RefinedType(parent, _) => isNullable(parent) - case AndType(tp1, tp2) => isNullable(tp1) && isNullable(tp2) - case OrType(tp1, tp2) => isNullable(tp1) || isNullable(tp2) - case _ => println(i"$tp of class ${tp.getClass} is not nullable"); false - } - (tp1.symbol eq NothingClass) && tp2.isInstanceOf[ValueType] || - (tp1.symbol eq NullClass) && isNullable(tp2) - } - case tp1: SingletonType => - isNewSubType(tp1.underlying.widenExpr, tp2) || { - // if tp2 == p.type and p: q.type then try tp1 <:< q.type as a last effort. - tp2 match { - case tp2: TermRef => - tp2.info match { - case tp2i: TermRef => - isSubType(tp1, tp2i) - case ExprType(tp2i: TermRef) if (ctx.phase.id > ctx.gettersPhase.id) => - isSubType(tp1, tp2i) case _ => - false + compareRefinedSlow } + case _ => + compareRefinedSlow + } + compareRefined + case OrType(tp21, tp22) => + eitherIsSubType(tp1, tp21, tp1, tp22) || fourthTry(tp1, tp2) + case tp2 @ MethodType(_, formals2) => + def compareMethod = tp1 match { + case tp1 @ MethodType(_, formals1) => + (tp1.signature sameParams tp2.signature) && + (if (Config.newMatch) subsumeParams(formals1, formals2, tp1.isJava, tp2.isJava) + else matchingParams(formals1, formals2, tp1.isJava, tp2.isJava)) && + tp1.isImplicit == tp2.isImplicit && // needed? + isSubType(tp1.resultType, tp2.resultType.subst(tp2, tp1)) case _ => false } - } - case tp1: RefinedType => + compareMethod + case tp2: PolyType => + def comparePoly = tp1 match { + case tp1: PolyType => + (tp1.signature sameParams tp2.signature) && + matchingTypeParams(tp1, tp2) && + isSubType(tp1.resultType, tp2.resultType.subst(tp2, tp1)) + case _ => + false + } + comparePoly + 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, pre) + case _ => isSubType(tp1.widenExpr, restpe2, pre) + } + compareExpr + case tp2 @ TypeBounds(lo2, hi2) => + def compareTypeBounds = tp1 match { + case tp1 @ TypeBounds(lo1, hi1) => + (tp2.variance > 0 && tp1.variance >= 0 || isSubType(lo2, lo1)) && + (tp2.variance < 0 && tp1.variance <= 0 || isSubType(hi1, hi2, pre)) + case tp1: ClassInfo => + val tt = tp1.typeRef + isSubType(lo2, tt) && isSubType(tt, hi2, pre) + case _ => + false + } + compareTypeBounds + case ClassInfo(pre2, cls2, _, _, _) => + def compareClassInfo = tp1 match { + case ClassInfo(pre1, cls1, _, _, _) => + (cls1 eq cls2) && isSubType(pre2, pre1) + case _ => + false + } + compareClassInfo + case JavaArrayType(elem2) => + def compareJavaArray = tp1 match { + case JavaArrayType(elem1) => isSubType(elem1, elem2) + case _ => fourthTry(tp1, tp2) + } + compareJavaArray + case _ => + fourthTry(tp1, tp2) + } + + def fourthTry(tp1: Type, tp2: Type): Boolean = tp1 match { + case tp1: TypeRef => + tp1.info match { + case TypeBounds(lo1, hi1) => + isSubType(hi1, tp2, pre) + case _ => + def isNullable(tp: Type): Boolean = tp.dealias match { + case tp: TypeRef => tp.symbol.isNullableClass + case RefinedType(parent, _) => isNullable(parent) + case AndType(tp1, tp2) => isNullable(tp1) && isNullable(tp2) + case OrType(tp1, tp2) => isNullable(tp1) || isNullable(tp2) + case _ => println(i"$tp of class ${tp.getClass} is not nullable"); false + } + (tp1.symbol eq NothingClass) && tp2.isInstanceOf[ValueType] || + (tp1.symbol eq NullClass) && isNullable(tp2) + } + case tp1: SingletonType => + isNewSubType(tp1.underlying.widenExpr, tp2) || { + // if tp2 == p.type and p: q.type then try tp1 <:< q.type as a last effort. + tp2 match { + case tp2: TermRef => + tp2.info match { + case tp2i: TermRef => + isSubType(tp1, tp2i, pre) + case ExprType(tp2i: TermRef) if (ctx.phase.id > ctx.gettersPhase.id) => + isSubType(tp1, tp2i, pre) + case _ => + false + } + case _ => + false + } + } + case tp1: RefinedType => { val saved = pendingRefinedBases try { addPendingName(tp1.refinedName, tp1, tp1) isNewSubType(tp1.parent, tp2) } finally pendingRefinedBases = saved - } || needsEtaLift(tp2, tp1) && tp2.testLifted(tp1.typeParams, isSubType(tp1, _)) - case AndType(tp11, tp12) => - eitherIsSubType(tp11, tp2, tp12, tp2) - case JavaArrayType(elem1) => - tp2 isRef ObjectClass - case _ => - false - } + } || needsEtaLift(tp2, tp1) && tp2.testLifted(tp1.typeParams, isSubType(tp1, _, pre)) + case AndType(tp11, tp12) => + eitherIsSubType(tp11, tp2, tp12, tp2) + case JavaArrayType(elem1) => + tp2 isRef ObjectClass + case _ => + false + } - /** Returns true iff either `tp11 <:< tp21` or `tp12 <:< tp22`, trying at the same time - * to keep the constraint as wide as possible. Specifically, 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. - */ - def eitherIsSubType(tp11: Type, tp21: Type, tp12: Type, tp22: Type) = { - val preConstraint = constraint - isSubType(tp11, tp21) && { - val leftConstraint = constraint - constraint = preConstraint - if (isSubType(tp12, tp22) && !subsumes(leftConstraint, constraint, preConstraint)) - constraint = leftConstraint - true - } || isSubType(tp12, tp22) - } + /** Returns true iff either `tp11 <:< tp21` or `tp12 <:< tp22`, trying at the same time + * to keep the constraint as wide as possible. Specifically, 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. + */ + def eitherIsSubType(tp11: Type, tp21: Type, tp12: Type, tp22: Type) = { + val preConstraint = constraint + isSubType(tp11, tp21, pre) && { + val leftConstraint = constraint + constraint = preConstraint + if (isSubType(tp12, tp22, pre) && !subsumes(leftConstraint, constraint, preConstraint)) + constraint = leftConstraint + true + } || isSubType(tp12, tp22, pre) + } - /** 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 + /** Like tp1 <:< tp2, but returns false immediately if we know that + * the case was covered previously during subtyping. + */ + def isNewSubType(tp1: Type, tp2: Type): Boolean = + if (isCovered(tp1) && isCovered(tp2)) { + //println(s"useless subtype: $tp1 <:< $tp2") + false + } else isSubType(tp1, tp2, pre) + + 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}") + 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 + } + } + } + + // begin isSubType + 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 < LogPendingSubTypesThreshold) firstTry(tp1, tp2) + else monitoredIsSubType(tp1, tp2) + recCount = recCount - 1 + if (!result) constraint = saved + else if (recCount == 0 && needsGc) state.gc() + + def recordStatistics = { + // Stats.record(s"isSubType ${tp1.show} <:< ${tp2.show}") + totalCount += 1 + if (result) successCount += 1 else successCount = savedSuccessCount + if (recCount == 0) { + Stats.record("successful subType", successCount) + Stats.record("total subType", totalCount) + successCount = 0 + totalCount = 0 + } + } + if (Stats.monitored) recordStatistics + + result + } catch { + case NonFatal(ex) => + def showState = { + println(disambiguated(implicit ctx => s"assertion failure for ${tp1.show} <:< ${tp2.show}, frozen = $frozenConstraint")) + def explainPoly(tp: Type) = tp match { + case tp: PolyParam => println(s"polyparam ${tp.show} found in ${tp.binder.show}") + case tp: TypeRef if tp.symbol.exists => println(s"typeref ${tp.show} found in ${tp.symbol.owner.show}") + case tp: TypeVar => println(s"typevar ${tp.show}, origin = ${tp.origin}") + case _ => println(s"${tp.show} is a ${tp.getClass}") + } + explainPoly(tp1) + explainPoly(tp2) + } + if (ex.isInstanceOf[AssertionError]) showState + recCount -= 1 + constraint = saved + successCount = savedSuccessCount + throw ex + } } - else isSubType(tp1, tp2) + } /** A type has been covered previously in subtype checking if it * is some combination of TypeRefs that point to classes, where the diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index b8e0a48c129c..469e8ca01d92 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -761,12 +761,12 @@ object Types { def lookupRefined(name: Name)(implicit ctx: Context): Type = { def dependsOnRefinedThis(tp: Type): Boolean = tp.stripTypeVar match { - case tp @ TypeRef(RefinedThis(rt), _) if rt refines this => + case tp @ TypeRef(RefinedThis(rt, _), _) if rt refines this => tp.info match { case TypeAlias(alias) => dependsOnRefinedThis(alias) case _ => true } - case RefinedThis(rt) => rt refines this + case RefinedThis(rt, _) => rt refines this case tp: NamedType => !tp.symbol.isStatic && dependsOnRefinedThis(tp.prefix) case tp: RefinedType => dependsOnRefinedThis(tp.refinedInfo) || dependsOnRefinedThis(tp.parent) @@ -783,7 +783,7 @@ object Types { case TypeAlias(tp) if !dependsOnRefinedThis(tp) => tp case _ => NoType } - case RefinedThis(rt) => + case RefinedThis(rt, _) => rt.lookupRefined(name) case pre: WildcardType => WildcardType @@ -1741,6 +1741,40 @@ object Types { extends CachedProxyType with BindingType with ValueType { val refinedInfo: Type + + private var containsRefinedThisCache: Boolean = _ + private var containsRefinedThisKnown: Boolean = false + + def containsRefinedThis(implicit ctx: Context): Boolean = { + def recur(tp: Type, level: Int): Boolean = tp.stripTypeVar match { + case tp @ TypeRef(RefinedThis(rt, `level`), _) => + tp.info match { + case TypeAlias(alias) => recur(alias, level) + case _ => true + } + case RefinedThis(rt, `level`) => + true + case tp: NamedType => + !tp.symbol.isStatic && recur(tp.prefix, level) + case tp: RefinedType => + recur(tp.refinedInfo, level + 1) || + recur(tp.parent, level) + case tp: TypeBounds => + recur(tp.lo, level) || + recur(tp.hi, level) + case tp: AnnotatedType => + recur(tp.underlying, level) + case tp: AndOrType => + recur(tp.tp1, level) || recur(tp.tp2, level) + case _ => + false + } + if (!containsRefinedThisKnown) { + containsRefinedThisCache = recur(refinedInfo, 0) + containsRefinedThisKnown = true + } + containsRefinedThisCache + } override def underlying(implicit ctx: Context) = parent @@ -2167,7 +2201,7 @@ object Types { override def computeHash = doHash(paramNum, binder) } - case class RefinedThis(binder: RefinedType) extends BoundType with SingletonType { + case class RefinedThis(binder: RefinedType, level: Int = 0) extends BoundType with SingletonType { type BT = RefinedType override def underlying(implicit ctx: Context) = binder def copyBoundType(bt: BT) = RefinedThis(bt) diff --git a/src/dotty/tools/dotc/printing/PlainPrinter.scala b/src/dotty/tools/dotc/printing/PlainPrinter.scala index e58775b65821..a125de7801cf 100644 --- a/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -207,7 +207,7 @@ class PlainPrinter(_ctx: Context) extends Printer { toText(value) case MethodParam(mt, idx) => nameString(mt.paramNames(idx)) - case RefinedThis(_) => + case RefinedThis(_, _) => "this" } } diff --git a/tests/pending/pos/compound.scala b/tests/pending/pos/compound.scala index 60890f9102b9..308ffdfd981b 100644 --- a/tests/pending/pos/compound.scala +++ b/tests/pending/pos/compound.scala @@ -7,3 +7,7 @@ abstract class Test { var xx: A with B { type T; val xz: T } = null; xx = yy; } +abstract class Test2 { + var yy: A with B { type T; val xz: T } = null; + val xx: A with B { type T; val xz: T } = yy +} diff --git a/tests/pending/pos/subtypcycle.scala b/tests/pending/pos/subtypcycle.scala new file mode 100644 index 000000000000..76eb7ffec4f8 --- /dev/null +++ b/tests/pending/pos/subtypcycle.scala @@ -0,0 +1,10 @@ +object subtypcycle { + trait Y { + type A <: { type T >: B } + type B >: { type T >: A } + } + + val y: Y = ??? + val a: y.A = ??? + val b: y.B = a +} diff --git a/tests/pos/Patterns.scala b/tests/pos/Patterns.scala index 54c4d8ab27b9..98af1cddbcb1 100644 --- a/tests/pos/Patterns.scala +++ b/tests/pos/Patterns.scala @@ -6,7 +6,7 @@ object Patterns { private def rebase(tp: NamedType): Type = { def rebaseFrom(prefix: Type): Type = ??? tp.prefix match { - case RefinedThis(rt) => rebaseFrom(rt) + case RefinedThis(rt, _) => rebaseFrom(rt) case pre: ThisType => rebaseFrom(pre) case _ => tp } diff --git a/tests/pos/refinedSubtyping.scala b/tests/pos/refinedSubtyping.scala index a01be181d65f..329c62314f98 100644 --- a/tests/pos/refinedSubtyping.scala +++ b/tests/pos/refinedSubtyping.scala @@ -60,3 +60,14 @@ class Test3 { y = x } +/* Does not work yet: +class Test4 { + + abstract class A { type T; val xz: Any } + + val yy: A { val xz: T } = null; +// val xx: A { val xz: T } = null; + val zz: A { val xz: T } = yy; + +} +*/ From 95f74c2a999b20ee5f6ddbe6e14511872e8c227f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 1 Jan 2015 14:47:52 +0100 Subject: [PATCH 05/72] Provide the correct levels for RefinedThis types. --- src/dotty/tools/dotc/core/Substituters.scala | 9 +++++++-- .../tools/dotc/core/TypeApplications.scala | 4 ++-- src/dotty/tools/dotc/core/TypeComparer.scala | 4 ++-- src/dotty/tools/dotc/core/TypeOps.scala | 17 ++++++++++++++++- src/dotty/tools/dotc/core/Types.scala | 10 +++++++--- .../tools/dotc/core/pickling/UnPickler.scala | 14 +++----------- src/dotty/tools/dotc/typer/Typer.scala | 2 +- 7 files changed, 38 insertions(+), 22 deletions(-) diff --git a/src/dotty/tools/dotc/core/Substituters.scala b/src/dotty/tools/dotc/core/Substituters.scala index 2051885075d7..119aca569739 100644 --- a/src/dotty/tools/dotc/core/Substituters.scala +++ b/src/dotty/tools/dotc/core/Substituters.scala @@ -181,8 +181,13 @@ trait Substituters { this: Context => final def substThis(tp: Type, from: RefinedType, to: Type, theMap: SubstRefinedThisMap): Type = tp match { - case tp @ RefinedThis(rt, _) => - if (rt eq from) to else tp + case tp @ RefinedThis(rt, level) => + if (rt eq from) + to match { // !!! TODO drop + case RefinedThis(rt1, -1) => RefinedThis(rt1, level) + case _ => to + } + else tp case tp: NamedType => if (tp.currentSymbol.isStatic) tp else tp.derivedSelect(substThis(tp.prefix, from, to, theMap)) diff --git a/src/dotty/tools/dotc/core/TypeApplications.scala b/src/dotty/tools/dotc/core/TypeApplications.scala index 6443c5054136..21aa5960e666 100644 --- a/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/src/dotty/tools/dotc/core/TypeApplications.scala @@ -409,7 +409,7 @@ class TypeApplications(val self: Type) extends AnyVal { def replacements(rt: RefinedType): List[Type] = for (sym <- boundSyms) - yield TypeRef(RefinedThis(rt), correspondingParamName(sym)) + yield TypeRef(RefinedThis(rt, 0), correspondingParamName(sym)) def rewrite(tp: Type): Type = tp match { case tp @ RefinedType(parent, name: TypeName) => @@ -453,7 +453,7 @@ class TypeApplications(val self: Type) extends AnyVal { val lambda = defn.lambdaTrait(boundSyms.map(_.variance)) val substitutedRHS = (rt: RefinedType) => { val argRefs = boundSyms.indices.toList.map(i => - RefinedThis(rt).select(tpnme.lambdaArgName(i))) + RefinedThis(rt, 0).select(tpnme.lambdaArgName(i))) tp.subst(boundSyms, argRefs).bounds.withVariance(1) } val res = RefinedType(lambda.typeRef, tpnme.Apply, substitutedRHS) diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index b8cc45fa7a9c..85c57b7447e8 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -309,7 +309,7 @@ class TypeComparer(initctx: Context) extends DotClass { } private def narrowRefined(tp: Type): Type = tp match { - case tp: RefinedType => RefinedThis(tp) + case tp: RefinedType => RefinedThis(tp, 0) // !!! TODO check that we can drop narrowRefined entirely case _ => tp } @@ -323,7 +323,7 @@ class TypeComparer(initctx: Context) extends DotClass { def rebaseFrom(prefix: Type): Type = { rebaseQual(prefix, tp.name, considerBoth = true) match { case rt: RefinedType if rt ne prefix => - tp.derivedSelect(RefinedThis(rt)).dealias // dealias to short-circuit cycles spanning type aliases or LazyRefs + tp.derivedSelect(RefinedThis(rt, 0)).dealias // dealias to short-circuit cycles spanning type aliases or LazyRefs case _ => tp } } diff --git a/src/dotty/tools/dotc/core/TypeOps.scala b/src/dotty/tools/dotc/core/TypeOps.scala index 260e2f6d6b9a..88f495f47fe9 100644 --- a/src/dotty/tools/dotc/core/TypeOps.scala +++ b/src/dotty/tools/dotc/core/TypeOps.scala @@ -330,7 +330,22 @@ trait TypeOps { this: Context => } parentRefs } - + + /** Map `C.this` types where `C` is `refineCls` to RefinedThis types with given level. + * The level gets adjusted for nested refined types. + */ + def thisToRefinedThis(rt: RefinedType, refineCls: Symbol, level: Int): TypeMap = new TypeMap { + def apply(tp: Type): Type = tp match { + case tp: ThisType if tp.cls eq refineCls => RefinedThis(rt, level) + case tp: RefinedType => + tp.derivedRefinedType( + this(tp.parent), tp.refinedName, + thisToRefinedThis(rt, refineCls, level + 1)(tp.refinedInfo)) + case _ => + mapOver(tp) + } + } + /** An argument bounds violation is a triple consisting of * - the argument tree * - a string "upper" or "lower" indicating which bound is violated diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 469e8ca01d92..d1b2e8f1e0ca 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -1805,7 +1805,7 @@ object Types { && !parent.isLambda) derivedRefinedType(parent.EtaExpand, refinedName, refinedInfo) else - RefinedType(parent, refinedName, rt => refinedInfo.substThis(this, RefinedThis(rt))) + RefinedType(parent, refinedName, rt => refinedInfo.substThis(this, RefinedThis(rt, -1))) // !!! TODO: replace with simply `refinedInfo` } override def equals(that: Any) = that match { @@ -2201,10 +2201,14 @@ object Types { override def computeHash = doHash(paramNum, binder) } - case class RefinedThis(binder: RefinedType, level: Int = 0) extends BoundType with SingletonType { + /** A reference to the `this` of an enclosing refined type. + * @param level The number of enclosing refined types between + * the `this` reference and its target. + */ + case class RefinedThis(binder: RefinedType, level: Int) extends BoundType with SingletonType { type BT = RefinedType override def underlying(implicit ctx: Context) = binder - def copyBoundType(bt: BT) = RefinedThis(bt) + def copyBoundType(bt: BT) = RefinedThis(bt, level) // need to customize hashCode and equals to prevent infinite recursion for // refinements that refer to the refinement type via this diff --git a/src/dotty/tools/dotc/core/pickling/UnPickler.scala b/src/dotty/tools/dotc/core/pickling/UnPickler.scala index 72804870014d..2bea977f7b5e 100644 --- a/src/dotty/tools/dotc/core/pickling/UnPickler.scala +++ b/src/dotty/tools/dotc/core/pickling/UnPickler.scala @@ -158,9 +158,6 @@ class UnPickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClassRoot: /** A map from symbols to their associated `decls` scopes */ private val symScopes = mutable.AnyRefMap[Symbol, Scope]() - /** A map from refinement classes to their associated refinement types */ - private val refinementTypes = mutable.AnyRefMap[Symbol, RefinedType]() - protected def errorBadSignature(msg: String, original: Option[RuntimeException] = None)(implicit ctx: Context) = { val ex = new BadSignature( sm"""error reading Scala signature of $classRoot from $source: @@ -612,9 +609,7 @@ class UnPickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClassRoot: case NOPREFIXtpe => NoPrefix case THIStpe => - val cls = readSymbolRef().asClass - if (isRefinementClass(cls)) RefinedThis(refinementTypes(cls)) - else cls.thisType + readSymbolRef().thisType case SINGLEtpe => val pre = readTypeRef() val sym = readDisambiguatedSymbolRef(_.info.isParameterless) @@ -666,11 +661,8 @@ class UnPickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClassRoot: if (decls.isEmpty) parent else { def addRefinement(tp: Type, sym: Symbol) = - RefinedType(tp, sym.name, sym.info) - val result = (parent /: decls.toList)(addRefinement).asInstanceOf[RefinedType] - assert(!refinementTypes.isDefinedAt(clazz), clazz + "/" + decls) - refinementTypes(clazz) = result - result + RefinedType(tp, sym.name, ctx.thisToRefinedThis(_, clazz, 0)(sym.info)) + (parent /: decls.toList)(addRefinement).asInstanceOf[RefinedType] } case CLASSINFOtpe => val clazz = readSymbolRef() diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index 0ff47b36d024..dd05fea3f6e8 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -791,7 +791,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit checkRefinementNonCyclic(refinement, refineCls, seen) val rsym = refinement.symbol val rinfo = if (rsym is Accessor) rsym.info.resultType else rsym.info - RefinedType(parent, rsym.name, rt => rinfo.substThis(refineCls, RefinedThis(rt))) + RefinedType(parent, rsym.name, ctx.thisToRefinedThis(_, refineCls, 0)(rinfo)) // todo later: check that refinement is within bounds } val res = cpy.RefinedTypeTree(tree)(tpt1, refinements1) withType From 390ac42ae28d2ea6fbd5da7446b85550c70b8ea7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 1 Jan 2015 15:44:21 +0100 Subject: [PATCH 06/72] Removed debug println --- src/dotty/tools/dotc/core/TypeComparer.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index 85c57b7447e8..93cf60f3beb4 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -718,7 +718,7 @@ class TypeComparer(initctx: Context) extends DotClass { case RefinedType(parent, _) => isNullable(parent) case AndType(tp1, tp2) => isNullable(tp1) && isNullable(tp2) case OrType(tp1, tp2) => isNullable(tp1) || isNullable(tp2) - case _ => println(i"$tp of class ${tp.getClass} is not nullable"); false + case _ => false } (tp1.symbol eq NothingClass) && tp2.isInstanceOf[ValueType] || (tp1.symbol eq NullClass) && isNullable(tp2) From 6543f8914cf1582d364d6c0e4395cd878dd6bd4d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 1 Jan 2015 15:45:16 +0100 Subject: [PATCH 07/72] Make use of refinementRefersToThis in lookupRefinement. That way we profit from caching. --- src/dotty/tools/dotc/core/Types.scala | 43 ++++++++++----------------- 1 file changed, 15 insertions(+), 28 deletions(-) diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index d1b2e8f1e0ca..99f919a86096 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -759,29 +759,16 @@ object Types { * a reference to the "this" of the current refined type. */ def lookupRefined(name: Name)(implicit ctx: Context): Type = { - - def dependsOnRefinedThis(tp: Type): Boolean = tp.stripTypeVar match { - case tp @ TypeRef(RefinedThis(rt, _), _) if rt refines this => - tp.info match { - case TypeAlias(alias) => dependsOnRefinedThis(alias) - case _ => true - } - case RefinedThis(rt, _) => rt refines this - case tp: NamedType => - !tp.symbol.isStatic && dependsOnRefinedThis(tp.prefix) - case tp: RefinedType => dependsOnRefinedThis(tp.refinedInfo) || dependsOnRefinedThis(tp.parent) - case tp: TypeBounds => dependsOnRefinedThis(tp.lo) || dependsOnRefinedThis(tp.hi) - case tp: AnnotatedType => dependsOnRefinedThis(tp.underlying) - case tp: AndOrType => dependsOnRefinedThis(tp.tp1) || dependsOnRefinedThis(tp.tp2) - case _ => false - } - def loop(pre: Type): Type = pre.stripTypeVar match { case pre: RefinedType => if (pre.refinedName ne name) loop(pre.parent) - else this.member(name).info match { - case TypeAlias(tp) if !dependsOnRefinedThis(tp) => tp - case _ => NoType + else pre.refinedInfo match { + case TypeAlias(tp) if !pre.refinementRefersToThis => + this.member(name).info match { + case TypeAlias(tp) => tp + case _ => NoType + } + case _ => loop(pre.parent) } case RefinedThis(rt, _) => rt.lookupRefined(name) @@ -1742,12 +1729,12 @@ object Types { val refinedInfo: Type - private var containsRefinedThisCache: Boolean = _ - private var containsRefinedThisKnown: Boolean = false + private var refinementRefersToThisCache: Boolean = _ + private var refinementRefersToThisKnown: Boolean = false - def containsRefinedThis(implicit ctx: Context): Boolean = { + def refinementRefersToThis(implicit ctx: Context): Boolean = { def recur(tp: Type, level: Int): Boolean = tp.stripTypeVar match { - case tp @ TypeRef(RefinedThis(rt, `level`), _) => + case tp @ TypeRef(RefinedThis(_, `level`), _) => tp.info match { case TypeAlias(alias) => recur(alias, level) case _ => true @@ -1769,11 +1756,11 @@ object Types { case _ => false } - if (!containsRefinedThisKnown) { - containsRefinedThisCache = recur(refinedInfo, 0) - containsRefinedThisKnown = true + if (!refinementRefersToThisKnown) { + refinementRefersToThisCache = recur(refinedInfo, 0) + refinementRefersToThisKnown = true } - containsRefinedThisCache + refinementRefersToThisCache } override def underlying(implicit ctx: Context) = parent From bd405e5ae89093c83f7914bd515d82dcdec22f32 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 1 Jan 2015 15:56:25 +0100 Subject: [PATCH 08/72] Extended refinementRefersToThis to alias types. Given class C { type T } C { type U = this.T } { val x: U } the second refinement does refer via the alias to the refinement type itself. --- src/dotty/tools/dotc/core/Types.scala | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 99f919a86096..68deae176263 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -1734,15 +1734,13 @@ object Types { def refinementRefersToThis(implicit ctx: Context): Boolean = { def recur(tp: Type, level: Int): Boolean = tp.stripTypeVar match { - case tp @ TypeRef(RefinedThis(_, `level`), _) => - tp.info match { - case TypeAlias(alias) => recur(alias, level) - case _ => true - } case RefinedThis(rt, `level`) => true case tp: NamedType => - !tp.symbol.isStatic && recur(tp.prefix, level) + tp.info match { + case TypeAlias(alias) => recur(alias, level) + case _ => !tp.symbol.isStatic && recur(tp.prefix, level) + } case tp: RefinedType => recur(tp.refinedInfo, level + 1) || recur(tp.parent, level) From 9c1d4868ee8003e6ce11b95b97ddabe347ba00cb Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 1 Jan 2015 22:24:21 +0100 Subject: [PATCH 09/72] isSubType reorg Capture original types in enclosing scope instead of passing them explicitly. --- src/dotty/tools/dotc/core/TypeComparer.scala | 211 ++++++++++--------- 1 file changed, 112 insertions(+), 99 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index 93cf60f3beb4..3a4a2305d673 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -308,11 +308,6 @@ class TypeComparer(initctx: Context) extends DotClass { } } - private def narrowRefined(tp: Type): Type = tp match { - case tp: RefinedType => RefinedThis(tp, 0) // !!! TODO check that we can drop narrowRefined entirely - case _ => tp - } - /** If the prefix of a named type is `this` (i.e. an instance of type * `ThisType` or `RefinedThis`), and there is a refinement type R that * "refines" (transitively contains as its parent) a class reference @@ -357,21 +352,75 @@ class TypeComparer(initctx: Context) extends DotClass { (tp2 eq AnyType) && tp1.isValueType) return true isSubType(tp1, tp2) } - - protected def isSubTypeWhenFrozen(tp1: Type, tp2: Type): Boolean = isSubTypeWhenFrozen(tp1, tp2, tp1) - protected def isSubTypeWhenFrozen(tp1: Type, tp2: Type, pre: Type): Boolean = { + + protected def isSubTypeWhenFrozen(tp1: Type, tp2: Type): Boolean = { val saved = frozenConstraint frozenConstraint = true - try isSubType(tp1, tp2, pre) + try isSubType(tp1, tp2) finally frozenConstraint = saved } private def traceInfo(tp1: Type, tp2: Type) = s"${tp1.show} <:< ${tp2.show}" + (if (ctx.settings.verbose.value) s" ${tp1.getClass} ${tp2.getClass}${if (frozenConstraint) " frozen" else ""}" else "") - - def isSubType(tp1: Type, tp2: Type): Boolean = isSubType(tp1, tp2, tp1) - def isSubType(tp1: Type, tp2: Type, pre: Type): Boolean = /*>|>*/ ctx.traceIndented(s"isSubType ${traceInfo(tp1, tp2)}", subtyping) /*<|<*/ { + + def isSubType(orig1: Type, orig2: Type): Boolean = { + + def ctdSubType(tp1: Type, tp2: Type): Boolean = /*>|>*/ ctx.traceIndented(s"isSubType ${traceInfo(orig1, tp2)}, orig1 = ${orig1.show}, class2 = ${tp2.getClass}", 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 < LogPendingSubTypesThreshold) firstTry(tp1, tp2) + else monitoredIsSubType(tp1, tp2) + recCount = recCount - 1 + if (!result) constraint = saved + else if (recCount == 0 && needsGc) state.gc() + + def recordStatistics = { + // Stats.record(s"isSubType ${tp1.show} <:< ${tp2.show}") + totalCount += 1 + if (result) successCount += 1 else successCount = savedSuccessCount + if (recCount == 0) { + Stats.record("successful subType", successCount) + Stats.record("total subType", totalCount) + successCount = 0 + totalCount = 0 + } + } + if (Stats.monitored) recordStatistics + + result + } catch { + case NonFatal(ex) => + def showState = { + println(disambiguated(implicit ctx => s"assertion failure for ${tp1.show} <:< ${tp2.show}, frozen = $frozenConstraint")) + def explainPoly(tp: Type) = tp match { + case tp: PolyParam => println(s"polyparam ${tp.show} found in ${tp.binder.show}") + case tp: TypeRef if tp.symbol.exists => println(s"typeref ${tp.show} found in ${tp.symbol.owner.show}") + case tp: TypeVar => println(s"typevar ${tp.show}, origin = ${tp.origin}") + case _ => println(s"${tp.show} is a ${tp.getClass}") + } + explainPoly(tp1) + explainPoly(tp2) + } + if (ex.isInstanceOf[AssertionError]) showState + recCount -= 1 + constraint = saved + successCount = savedSuccessCount + throw ex + } + } + } + + def narrowRefined(tp: Type): Type = tp match { + case tp: RefinedType => RefinedThis(tp, 0) // !!! TODO check that we can drop narrowRefined entirely + case _ => tp + } def firstTry(tp1: Type, tp2: Type): Boolean = { tp2 match { @@ -419,9 +468,9 @@ class TypeComparer(initctx: Context) extends DotClass { case tp2: PolyParam => def comparePolyParam = tp2 == tp1 || { - if (solvedConstraint && (constraint contains tp2)) isSubType(tp1, bounds(tp2).lo, pre) + if (solvedConstraint && (constraint contains tp2)) ctdSubType(tp1, bounds(tp2).lo) else - isSubTypeWhenFrozen(tp1, bounds(tp2).lo) || { + ctdSubTypeWhenFrozen(tp1, bounds(tp2).lo) || { if (isConstrained(tp2)) addConstraint(tp2, tp1.widenExpr, fromBelow = true) else (ctx.mode is Mode.TypevarsMissContext) || secondTry(tp1, tp2) } @@ -430,17 +479,17 @@ class TypeComparer(initctx: Context) extends DotClass { case tp2: BoundType => tp2 == tp1 || secondTry(tp1, tp2) case tp2: TypeVar => - isSubType(tp1, tp2.underlying, pre) + ctdSubType(tp1, tp2.underlying) case tp2: WildcardType => def compareWild = tp2.optBounds match { - case TypeBounds(_, hi) => isSubType(tp1, hi, pre) + case TypeBounds(_, hi) => ctdSubType(tp1, hi) case NoType => true } compareWild case tp2: LazyRef => - isSubType(tp1, tp2.ref, pre) + ctdSubType(tp1, tp2.ref) case tp2: AnnotatedType => - isSubType(tp1, tp2.tpe, pre) // todo: refine? + ctdSubType(tp1, tp2.tpe) // todo: refine? case tp2: ThisType => tp1 match { case tp1: ThisType => @@ -454,13 +503,13 @@ class TypeComparer(initctx: Context) extends DotClass { case tp2: SuperType => tp1 match { case tp1: SuperType => - isSubType(tp1.thistpe, tp2.thistpe, pre) && + ctdSubType(tp1.thistpe, tp2.thistpe) && isSameType(tp1.supertpe, tp2.supertpe) case _ => secondTry(tp1, tp2) } case AndType(tp21, tp22) => - isSubType(tp1, tp21, pre) && isSubType(tp1, tp22, pre) + ctdSubType(tp1, tp21) && ctdSubType(tp1, tp22) case ErrorType => true case _ => @@ -477,13 +526,13 @@ class TypeComparer(initctx: Context) extends DotClass { secondTryNamed(tp1, tp2) } case OrType(tp11, tp12) => - isSubType(tp11, tp2, pre) && isSubType(tp12, tp2, pre) + ctdSubType(tp11, tp2) && ctdSubType(tp12, tp2) case tp1: PolyParam => def comparePolyParam = tp1 == tp2 || { - if (solvedConstraint && (constraint contains tp1)) isSubType(bounds(tp1).lo, tp2, pre) + if (solvedConstraint && (constraint contains tp1)) ctdSubType(bounds(tp1).lo, tp2) else - isSubTypeWhenFrozen(bounds(tp1).hi, tp2, pre) || { + ctdSubTypeWhenFrozen(bounds(tp1).hi, tp2) || { if (isConstrained(tp1)) addConstraint(tp1, tp2, fromBelow = false) && { if ((!frozenConstraint) && @@ -507,17 +556,17 @@ class TypeComparer(initctx: Context) extends DotClass { case tp1: BoundType => tp1 == tp2 || thirdTry(tp1, tp2) case tp1: TypeVar => - (tp1 eq tp2) || isSubType(tp1.underlying, tp2, pre) + (tp1 eq tp2) || ctdSubType(tp1.underlying, tp2) case tp1: WildcardType => def compareWild = tp1.optBounds match { - case TypeBounds(lo, _) => isSubType(lo, tp2, pre) + case TypeBounds(lo, _) => ctdSubType(lo, tp2) case _ => true } compareWild case tp1: LazyRef => - isSubType(tp1.ref, tp2, pre) + ctdSubType(tp1.ref, tp2) case tp1: AnnotatedType => - isSubType(tp1.tpe, tp2, pre) + ctdSubType(tp1.tpe, tp2) case ErrorType => true case _ => @@ -527,7 +576,7 @@ class TypeComparer(initctx: Context) extends DotClass { def secondTryNamed(tp1: NamedType, tp2: Type): Boolean = { def tryRebase2nd = { val tp1rebased = rebase(tp1) - if (tp1rebased ne tp1) isSubType(tp1rebased, tp2) + if (tp1rebased ne tp1) ctdSubType(tp1rebased, tp2) else thirdTry(tp1, tp2) } tp1.info match { @@ -550,11 +599,11 @@ class TypeComparer(initctx: Context) extends DotClass { case TypeBounds(lo1, hi1) => val gbounds1 = ctx.gadt.bounds(tp1.symbol) if (gbounds1 != null) - isSubTypeWhenFrozen(gbounds1.hi, tp2, pre) || + ctdSubTypeWhenFrozen(gbounds1.hi, tp2) || (ctx.mode is Mode.GADTflexible) && narrowGADTBounds(tp1, TypeBounds(gbounds1.lo, gbounds1.hi & tp2)) || tryRebase2nd - else if (lo1 eq hi1) isSubType(hi1, tp2, pre) + else if (lo1 eq hi1) ctdSubType(hi1, tp2) else tryRebase2nd case _ => tryRebase2nd @@ -565,26 +614,26 @@ class TypeComparer(initctx: Context) extends DotClass { case tp2: NamedType => def tryRebase3rd = { val tp2rebased = rebase(tp2) - if (tp2rebased ne tp2) isSubType(tp1, tp2rebased) + if (tp2rebased ne tp2) ctdSubType(tp1, tp2rebased) else fourthTry(tp1, tp2) } def compareNamed: Boolean = tp2.info match { case TypeBounds(lo2, hi2) => val gbounds2 = ctx.gadt.bounds(tp2.symbol) if (gbounds2 != null) - isSubTypeWhenFrozen(tp1, gbounds2.lo, pre) || + ctdSubTypeWhenFrozen(tp1, gbounds2.lo) || (ctx.mode is Mode.GADTflexible) && narrowGADTBounds(tp2, TypeBounds(gbounds2.lo | tp1, gbounds2.hi)) || fourthTry(tp1, tp2) else - ((frozenConstraint || !isCappable(tp1)) && isSubType(tp1, lo2, pre) + ((frozenConstraint || !isCappable(tp1)) && ctdSubType(tp1, lo2) || tryRebase3rd) case _ => val cls2 = tp2.symbol if (cls2.isClass) { val base = tp1.baseTypeRef(cls2) - if (base.exists && (base ne tp1)) return isSubType(base, tp2, pre) + if (base.exists && (base ne tp1)) return ctdSubType(base, tp2) if (cls2 == defn.SingletonClass && tp1.isStable) return true } tryRebase3rd @@ -599,6 +648,11 @@ class TypeComparer(initctx: Context) extends DotClass { def compareRefinedSlow: Boolean = { def hasMatchingMember(name: Name): Boolean = /*>|>*/ ctx.traceIndented(s"hasMatchingMember($name) ${tp1.member(name).info.show}", subtyping) /*<|<*/ { val tp1r = rebaseQual(tp1, name) + //val old = memberMatches(narrowRefined(tp1r) member name) + //val now = memberMatches(orig1.ensureSingleton member name) + //if (old != now) + // assert(false, i"discrepancy: $tp1 <:< $tp2, narrowRefined = ${narrowRefined(tp1r)} -> $old, ensureSingle = ${orig1.ensureSingleton}: $orig1 -> $now") + (memberMatches(narrowRefined(tp1r) member name) || { // special case for situations like: @@ -614,14 +668,14 @@ class TypeComparer(initctx: Context) extends DotClass { val saved = pendingRefinedBases try { addPendingName(name2, tp2, tp2) - isSubType(tp1, parent2, pre) + ctdSubType(tp1, parent2) } finally pendingRefinedBases = saved } (matchesParent && ( name2 == nme.WILDCARD || hasMatchingMember(name2) || fourthTry(tp1, tp2)) - || needsEtaLift(tp1, tp2) && tp1.testLifted(tp2.typeParams, isSubType(_, tp2, pre))) + || needsEtaLift(tp1, tp2) && tp1.testLifted(tp2.typeParams, ctdSubType(_, tp2))) } def compareRefined: Boolean = tp1.widen match { case tp1 @ RefinedType(parent1, name1) if name1 == name2 && name1.isTypeName => @@ -631,7 +685,7 @@ class TypeComparer(initctx: Context) extends DotClass { val saved = pendingRefinedBases try { addPendingName(name1, tp1, tp2) - isSubType(parent1, parent2, pre) + ctdSubType(parent1, parent2) } finally pendingRefinedBases = saved } case _ => @@ -673,18 +727,18 @@ class TypeComparer(initctx: Context) extends DotClass { // 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, pre) - case _ => isSubType(tp1.widenExpr, restpe2, pre) + case tp1 @ MethodType(Nil, _) => ctdSubType(tp1.resultType, restpe2) + case _ => ctdSubType(tp1.widenExpr, restpe2) } compareExpr case tp2 @ TypeBounds(lo2, hi2) => def compareTypeBounds = tp1 match { case tp1 @ TypeBounds(lo1, hi1) => (tp2.variance > 0 && tp1.variance >= 0 || isSubType(lo2, lo1)) && - (tp2.variance < 0 && tp1.variance <= 0 || isSubType(hi1, hi2, pre)) + (tp2.variance < 0 && tp1.variance <= 0 || ctdSubType(hi1, hi2)) case tp1: ClassInfo => val tt = tp1.typeRef - isSubType(lo2, tt) && isSubType(tt, hi2, pre) + ctdSubType(lo2, tt) && isSubType(tt, hi2) case _ => false } @@ -711,7 +765,7 @@ class TypeComparer(initctx: Context) extends DotClass { case tp1: TypeRef => tp1.info match { case TypeBounds(lo1, hi1) => - isSubType(hi1, tp2, pre) + ctdSubType(hi1, tp2) case _ => def isNullable(tp: Type): Boolean = tp.dealias match { case tp: TypeRef => tp.symbol.isNullableClass @@ -730,9 +784,9 @@ class TypeComparer(initctx: Context) extends DotClass { case tp2: TermRef => tp2.info match { case tp2i: TermRef => - isSubType(tp1, tp2i, pre) + ctdSubType(tp1, tp2i) case ExprType(tp2i: TermRef) if (ctx.phase.id > ctx.gettersPhase.id) => - isSubType(tp1, tp2i, pre) + ctdSubType(tp1, tp2i) case _ => false } @@ -747,7 +801,7 @@ class TypeComparer(initctx: Context) extends DotClass { isNewSubType(tp1.parent, tp2) } finally pendingRefinedBases = saved - } || needsEtaLift(tp2, tp1) && tp2.testLifted(tp1.typeParams, isSubType(tp1, _, pre)) + } || needsEtaLift(tp2, tp1) && tp2.testLifted(tp1.typeParams, ctdSubType(tp1, _)) case AndType(tp11, tp12) => eitherIsSubType(tp11, tp2, tp12, tp2) case JavaArrayType(elem1) => @@ -786,13 +840,13 @@ class TypeComparer(initctx: Context) extends DotClass { */ def eitherIsSubType(tp11: Type, tp21: Type, tp12: Type, tp22: Type) = { val preConstraint = constraint - isSubType(tp11, tp21, pre) && { + ctdSubType(tp11, tp21) && { val leftConstraint = constraint constraint = preConstraint - if (isSubType(tp12, tp22, pre) && !subsumes(leftConstraint, constraint, preConstraint)) + if (ctdSubType(tp12, tp22) && !subsumes(leftConstraint, constraint, preConstraint)) constraint = leftConstraint true - } || isSubType(tp12, tp22, pre) + } || ctdSubType(tp12, tp22) } /** Like tp1 <:< tp2, but returns false immediately if we know that @@ -802,7 +856,14 @@ class TypeComparer(initctx: Context) extends DotClass { if (isCovered(tp1) && isCovered(tp2)) { //println(s"useless subtype: $tp1 <:< $tp2") false - } else isSubType(tp1, tp2, pre) + } else ctdSubType(tp1, tp2) + + def ctdSubTypeWhenFrozen(tp1: Type, tp2: Type): Boolean = { + val saved = frozenConstraint + frozenConstraint = true + try ctdSubType(tp1, tp2) + finally frozenConstraint = saved + } def monitoredIsSubType(tp1: Type, tp2: Type) = { if (pendingSubTypes == null) { @@ -823,56 +884,8 @@ class TypeComparer(initctx: Context) extends DotClass { } } } - - // begin isSubType - 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 < LogPendingSubTypesThreshold) firstTry(tp1, tp2) - else monitoredIsSubType(tp1, tp2) - recCount = recCount - 1 - if (!result) constraint = saved - else if (recCount == 0 && needsGc) state.gc() - - def recordStatistics = { - // Stats.record(s"isSubType ${tp1.show} <:< ${tp2.show}") - totalCount += 1 - if (result) successCount += 1 else successCount = savedSuccessCount - if (recCount == 0) { - Stats.record("successful subType", successCount) - Stats.record("total subType", totalCount) - successCount = 0 - totalCount = 0 - } - } - if (Stats.monitored) recordStatistics - - result - } catch { - case NonFatal(ex) => - def showState = { - println(disambiguated(implicit ctx => s"assertion failure for ${tp1.show} <:< ${tp2.show}, frozen = $frozenConstraint")) - def explainPoly(tp: Type) = tp match { - case tp: PolyParam => println(s"polyparam ${tp.show} found in ${tp.binder.show}") - case tp: TypeRef if tp.symbol.exists => println(s"typeref ${tp.show} found in ${tp.symbol.owner.show}") - case tp: TypeVar => println(s"typevar ${tp.show}, origin = ${tp.origin}") - case _ => println(s"${tp.show} is a ${tp.getClass}") - } - explainPoly(tp1) - explainPoly(tp2) - } - if (ex.isInstanceOf[AssertionError]) showState - recCount -= 1 - constraint = saved - successCount = savedSuccessCount - throw ex - } - } + + ctdSubType(orig1, orig2) } /** A type has been covered previously in subtype checking if it From a7a603fc8ec431b261e7bceddfb61a935d8ad07b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 3 Jan 2015 18:50:34 +0100 Subject: [PATCH 10/72] Show level for RefinedThis Temporary solution for better diagnsitics. --- src/dotty/tools/dotc/printing/PlainPrinter.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/printing/PlainPrinter.scala b/src/dotty/tools/dotc/printing/PlainPrinter.scala index a125de7801cf..fd6ba44a7fec 100644 --- a/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -207,8 +207,8 @@ class PlainPrinter(_ctx: Context) extends Printer { toText(value) case MethodParam(mt, idx) => nameString(mt.paramNames(idx)) - case RefinedThis(_, _) => - "this" + case RefinedThis(_, level) => + s"this<$level>" } } From 14f6eb056f9959f178134762e0c6ce78e1dccc28 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 3 Jan 2015 19:08:03 +0100 Subject: [PATCH 11/72] Make findMember depend on RefinedThis level. When doing a findMember we now look at the level of a RefinedThis instead of its `rt` field. This required several fixes to how levels are assigned to RefinedThis types. --- src/dotty/tools/dotc/core/Substituters.scala | 80 +++++++++++++++++-- .../tools/dotc/core/TypeApplications.scala | 12 +-- src/dotty/tools/dotc/core/TypeComparer.scala | 4 + src/dotty/tools/dotc/core/Types.scala | 72 +++++++++++++++-- 4 files changed, 145 insertions(+), 23 deletions(-) diff --git a/src/dotty/tools/dotc/core/Substituters.scala b/src/dotty/tools/dotc/core/Substituters.scala index 119aca569739..9fd01ab564cb 100644 --- a/src/dotty/tools/dotc/core/Substituters.scala +++ b/src/dotty/tools/dotc/core/Substituters.scala @@ -1,6 +1,6 @@ package dotty.tools.dotc.core -import Types._, Symbols._, Contexts._ +import Types._, Symbols._, Contexts._, Names._ /** Substitution operations on types. See the corresponding `subst` and * `substThis` methods on class Type for an explanation. @@ -179,7 +179,9 @@ trait Substituters { this: Context => .mapOver(tp) } - final def substThis(tp: Type, from: RefinedType, to: Type, theMap: SubstRefinedThisMap): Type = + // !!! TODO remove once we are sure new RefinedThis scheme works and we drop the ref to + // the target type from RefinedThis. + final def substThis0(tp: Type, from: RefinedType, to: Type, theMap: SubstRefinedThisMap0): Type = tp match { case tp @ RefinedThis(rt, level) => if (rt eq from) @@ -190,16 +192,39 @@ trait Substituters { this: Context => else tp case tp: NamedType => if (tp.currentSymbol.isStatic) tp - else tp.derivedSelect(substThis(tp.prefix, from, to, theMap)) + else tp.derivedSelect(substThis0(tp.prefix, from, to, theMap)) case _: ThisType | _: BoundType | NoPrefix => tp case tp: RefinedType => - tp.derivedRefinedType(substThis(tp.parent, from, to, theMap), tp.refinedName, substThis(tp.refinedInfo, from, to, theMap)) + tp.derivedRefinedType(substThis0(tp.parent, from, to, theMap), tp.refinedName, substThis0(tp.refinedInfo, from, to, theMap)) case tp: TypeAlias => - tp.derivedTypeAlias(substThis(tp.alias, from, to, theMap)) + tp.derivedTypeAlias(substThis0(tp.alias, from, to, theMap)) case _ => - (if (theMap != null) theMap else new SubstRefinedThisMap(from, to)) + (if (theMap != null) theMap else new SubstRefinedThisMap0(from, to)) .mapOver(tp) + } + + final def substRefinedThis(tp: Type, level: Int, to: Type, theMap: SubstRefinedThisMap): Type = + tp match { + case tp @ RefinedThis(rt, l) if l == level => + to + case tp: NamedType => + if (tp.currentSymbol.isStatic) tp + else tp.derivedSelect(substRefinedThis(tp.prefix, level, to, theMap)) + case _: ThisType | _: BoundType | NoPrefix => + tp + case tp: RefinedType => + tp.derivedRefinedType( + substRefinedThis(tp.parent, level, to, theMap), tp.refinedName, + substRefinedThis(tp.refinedInfo, level + 1, to, theMap)) + case tp: TypeAlias => + tp.derivedTypeAlias(substRefinedThis(tp.alias, level, to, theMap)) + case _ => + val m = if (theMap != null) theMap else new SubstRefinedThisMap(to) + val saved = m.level + m.level = level + try m.mapOver(tp) + finally m.level = saved } final def substParam(tp: Type, from: ParamType, to: Type, theMap: SubstParamMap): Type = @@ -271,8 +296,13 @@ trait Substituters { this: Context => def apply(tp: Type): Type = substThis(tp, from, to, this) } - final class SubstRefinedThisMap(from: RefinedType, to: Type) extends DeepTypeMap { - def apply(tp: Type): Type = substThis(tp, from, to, this) + final class SubstRefinedThisMap0(from: RefinedType, to: Type) extends DeepTypeMap { + def apply(tp: Type): Type = substThis0(tp, from, to, this) + } + + final class SubstRefinedThisMap(to: Type) extends DeepTypeMap { + var level: Int = 0 + def apply(tp: Type): Type = substRefinedThis(tp, level, to, this) } final class SubstParamMap(from: ParamType, to: Type) extends DeepTypeMap { @@ -282,4 +312,38 @@ trait Substituters { this: Context => final class SubstParamsMap(from: BindingType, to: List[Type]) extends DeepTypeMap { def apply(tp: Type) = substParams(tp, from, to, this) } + + /** Substitute every occurrence of symbol `from_i` with `RefinedThis(leve).select(to_i)` + * where level represents the nesting level of the occurrence (i.e. number + * of refinedInfos between substituted type and the occurrence. + * TODO: Drop `rt` once it is dropped from RefinedThis + * TODO: Apply to enclosing refined type instead of refined info. That way we + * can optimize for types not containing any RefinedThis + */ + final class SubstWithRefinedSelectMap(rt: RefinedType, from: List[Symbol], to: List[TypeName]) extends DeepTypeMap { + private var level = 0 + def apply(tp: Type): Type = tp match { + case tp: NamedType => + val sym = tp.symbol + var fs = from + var ts = to + while (fs.nonEmpty) { + if (fs.head eq sym) + return RefinedThis(rt, level).select(ts.head) + fs = fs.tail + ts = ts.tail + } + if (sym.isStatic && !existsStatic(from)) tp + else tp.derivedSelect(apply(tp.prefix)) + case tp: RefinedType => + val parent1 = apply(tp.parent) + level += 1 + try tp.derivedRefinedType(parent1, tp.refinedName, apply(tp.refinedInfo)) + finally level -= 1 + case tp: TypeAlias => + tp.derivedTypeAlias(apply(tp.alias)) + case _ => + mapOver(tp) + } + } } \ No newline at end of file diff --git a/src/dotty/tools/dotc/core/TypeApplications.scala b/src/dotty/tools/dotc/core/TypeApplications.scala index 21aa5960e666..0a3884ded809 100644 --- a/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/src/dotty/tools/dotc/core/TypeApplications.scala @@ -407,17 +407,14 @@ class TypeApplications(val self: Type) extends AnyVal { if (bsyms.isEmpty) { val correspondingNames = correspondingParamName.values.toSet - def replacements(rt: RefinedType): List[Type] = - for (sym <- boundSyms) - yield TypeRef(RefinedThis(rt, 0), correspondingParamName(sym)) - def rewrite(tp: Type): Type = tp match { case tp @ RefinedType(parent, name: TypeName) => if (correspondingNames contains name) rewrite(parent) else RefinedType( rewrite(parent), name, - rt => tp.refinedInfo.subst(boundSyms, replacements(rt))) + new ctx.SubstWithRefinedSelectMap( + _, boundSyms, boundSyms map correspondingParamName)(tp.refinedInfo)) case tp => tp } @@ -452,9 +449,8 @@ class TypeApplications(val self: Type) extends AnyVal { def expand(tp: Type) = { val lambda = defn.lambdaTrait(boundSyms.map(_.variance)) val substitutedRHS = (rt: RefinedType) => { - val argRefs = boundSyms.indices.toList.map(i => - RefinedThis(rt, 0).select(tpnme.lambdaArgName(i))) - tp.subst(boundSyms, argRefs).bounds.withVariance(1) + val argNames = boundSyms.indices.toList.map(tpnme.lambdaArgName) + new ctx.SubstWithRefinedSelectMap(rt, boundSyms, argNames)(tp).bounds.withVariance(1) } val res = RefinedType(lambda.typeRef, tpnme.Apply, substitutedRHS) //println(i"lambda abstract $self wrt $boundSyms%, % --> $res") diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index 3a4a2305d673..9807af9012cd 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -372,6 +372,8 @@ class TypeComparer(initctx: Context) extends DotClass { else { val saved = constraint val savedSuccessCount = successCount + val savedRLC = Types.reverseLevelCheck // !!! TODO: remove + Types.reverseLevelCheck = false try { recCount = recCount + 1 val result = @@ -413,6 +415,8 @@ class TypeComparer(initctx: Context) extends DotClass { constraint = saved successCount = savedSuccessCount throw ex + } finally { + Types.reverseLevelCheck = savedRLC } } } diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 68deae176263..c6913eb0f2cf 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -436,7 +436,15 @@ object Types { } def goRefined(tp: RefinedType) = { val pdenot = go(tp.parent) - val rinfo = tp.refinedInfo.substThis(tp, pre) + val rinfo = pre match { + case pre: RefinedType => tp.refinedInfo.substThis0(tp, RefinedThis(pre, -1)) + case _ => tp.refinedInfo.substRefinedThis(0, pre) + } + if (Types.goRefinedCheck) { + val rinfo0 = tp.refinedInfo.substThis0(tp, pre) + if ((rinfo0 ne rinfo) && (rinfo0.show != rinfo.show)) + println(s"findMember discrepancy for $tp , $name, pre = $pre, old = $rinfo0, new = $rinfo") + } if (name.isTypeName) { // simplified case that runs more efficiently val jointInfo = if (rinfo.isAlias) rinfo else pdenot.info & rinfo pdenot.asSingleDenotation.derivedSingleDenotation(pdenot.symbol, jointInfo) @@ -747,6 +755,12 @@ object Types { def narrow(implicit ctx: Context): TermRef = TermRef(NoPrefix, ctx.newSkolem(this)) + def ensureSingleton(implicit ctx: Context): SingletonType = stripTypeVar match { + case tp: SingletonType => tp + case tp: ValueType => narrow + case tp: TypeProxy => tp.underlying.ensureSingleton + } + // ----- Normalizing typerefs over refined types ---------------------------- /** If this is a refinement type that has a refinement for `name` (which might be followed @@ -931,9 +945,13 @@ object Types { final def substThisUnlessStatic(cls: ClassSymbol, tp: Type)(implicit ctx: Context): Type = if (cls.isStaticOwner) this else ctx.substThis(this, cls, tp, null) - /** Substitute all occurrences of `RefinedThis(rt)` by `tp` */ - final def substThis(rt: RefinedType, tp: Type)(implicit ctx: Context): Type = - ctx.substThis(this, rt, tp, null) + /** Substitute all occurrences of `RefinedThis(rt)` by `tp` !!! TODO remove */ + final def substThis0(rt: RefinedType, tp: Type)(implicit ctx: Context): Type = + ctx.substThis0(this, rt, tp, null) + + /** Substitute all occurrences of `RefinedThis(level)` by `tp` */ + final def substRefinedThis(level: Int, to: Type)(implicit ctx: Context): Type = + ctx.substRefinedThis(this, level, to, null) /** Substitute a bound type by some other type */ final def substParam(from: ParamType, to: Type)(implicit ctx: Context): Type = @@ -1760,10 +1778,47 @@ object Types { } refinementRefersToThisCache } + + def checkLevel(implicit ctx: Context): Unit = { + val checkAccu = new TypeAccumulator[Unit] { + var level = 0 + def apply(x: Unit, tp: Type) = tp.stripTypeVar match { + case RefinedThis(rt, l) => + def dominates(tp: Type, rt: RefinedType): Boolean = + (tp eq rt) || { + tp match { + case RefinedType(parent, _) => dominates(parent, rt) + case _ => false + } + } + if (rt eq RefinedType.this) assert(l == level, RefinedType.this) + if (Types.reverseLevelCheck && l == level) + assert(dominates(rt, RefinedType.this) || dominates(RefinedType.this, rt), + RefinedType.this) + case tp: RefinedType => + level += 1 + apply(x, tp.refinedInfo) + level -= 1 + apply(x, tp.parent) + case tp: TypeBounds => + apply(x, tp.lo) + apply(x, tp.hi) + case tp: AnnotatedType => + apply(x, tp.underlying) + case tp: AndOrType => + apply(x, tp.tp1) + apply(x, tp.tp2) + case _ => + foldOver(x, tp) + } + } + checkAccu((), refinedInfo) + } override def underlying(implicit ctx: Context) = parent private def checkInst(implicit ctx: Context): this.type = { + checkLevel if (Config.checkLambdaVariance) refinedInfo match { case refinedInfo: TypeBounds if refinedInfo.variance != 0 && refinedName.isLambdaArgName => @@ -1790,7 +1845,7 @@ object Types { && !parent.isLambda) derivedRefinedType(parent.EtaExpand, refinedName, refinedInfo) else - RefinedType(parent, refinedName, rt => refinedInfo.substThis(this, RefinedThis(rt, -1))) // !!! TODO: replace with simply `refinedInfo` + RefinedType(parent, refinedName, rt => refinedInfo.substThis0(this, RefinedThis(rt, -1))) // !!! TODO: replace with simply `refinedInfo` } override def equals(that: Any) = that match { @@ -1802,7 +1857,7 @@ object Types { false } override def computeHash = doHash(refinedName, refinedInfo, parent) - override def toString = s"RefinedType($parent, $refinedName, $refinedInfo)" + override def toString = s"RefinedType($parent, $refinedName, $refinedInfo | $hashCode)" // !!! TODO: remove } class CachedRefinedType(parent: Type, refinedName: Name, infoFn: RefinedType => Type) extends RefinedType(parent, refinedName) { @@ -2202,7 +2257,7 @@ object Types { case that: RefinedThis => this.binder eq that.binder case _ => false } - override def toString = s"RefinedThis(${binder.hashCode})" + override def toString = s"RefinedThis($level, ${binder.hashCode})" } // ------------ Type variables ---------------------------------------- @@ -3011,6 +3066,9 @@ object Types { // ----- Debug --------------------------------------------------------- var debugTrace = false + + var reverseLevelCheck = false + var goRefinedCheck = false val watchList = List[String]( ) map (_.toTypeName) From 2085ebbeb36c085ede54b8538dd9b74f8cf8a9e6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 6 Jan 2015 11:08:10 +0100 Subject: [PATCH 12/72] Deleted diagnostics output that caused cyclic references Cyclic reference was caused when compiling pos/i94-nada.scala with typer = new Printer and -Yfront set. --- src/dotty/tools/dotc/ast/Desugar.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/src/dotty/tools/dotc/ast/Desugar.scala b/src/dotty/tools/dotc/ast/Desugar.scala index 32ada504daa0..7cdedb19a6cb 100644 --- a/src/dotty/tools/dotc/ast/Desugar.scala +++ b/src/dotty/tools/dotc/ast/Desugar.scala @@ -59,7 +59,6 @@ object desugar { case tp: NamedType if tp.symbol.owner eq originalOwner => val defctx = ctx.outersIterator.dropWhile(_.scope eq ctx.scope).next var local = defctx.denotNamed(tp.name).suchThat(_ is ParamOrAccessor).symbol - typr.println(s"rewiring ${tp.symbol} from ${originalOwner.showLocated} to ${local.showLocated}, current owner = ${ctx.owner.showLocated}") if (local.exists) (defctx.owner.thisType select local).dealias else throw new Error(s"no matching symbol for ${sym.showLocated} in ${defctx.owner} / ${defctx.effectiveScope}") case _ => From 5e6114911cb11336bb53938f2f3b3a70c328921a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 7 Jan 2015 13:16:22 +0100 Subject: [PATCH 13/72] Fixed bug that caused error message to be suppressed. Symptom was: When compiling Definitions.scala with the changes in the subsequent commits, an empty tree was passed as implicit parameter, without an "implicit not found" error being reported. The problem needed a fix in TyperState. --- src/dotty/tools/dotc/core/TyperState.scala | 17 ++++++++--------- test/dotc/tests.scala | 3 --- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/dotty/tools/dotc/core/TyperState.scala b/src/dotty/tools/dotc/core/TyperState.scala index de5e0a96168c..86dc0d24e1cd 100644 --- a/src/dotty/tools/dotc/core/TyperState.scala +++ b/src/dotty/tools/dotc/core/TyperState.scala @@ -164,17 +164,16 @@ extends TyperState(r) { * found a better solution. */ override def tryWithFallback[T](op: => T)(fallback: => T)(implicit ctx: Context): T = { + val storeReporter = new StoreReporter val savedReporter = myReporter + myReporter = storeReporter val savedConstraint = myConstraint - myReporter = new StoreReporter - val result = op - try - if (!reporter.hasErrors) result - else { - myConstraint = savedConstraint - fallback - } - finally myReporter = savedReporter + val result = try op finally myReporter = savedReporter + if (!storeReporter.hasErrors) result + else { + myConstraint = savedConstraint + fallback + } } override def toText(printer: Printer): Text = constraint.toText(printer) diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index 3f1170e40096..e12becd0c740 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -24,9 +24,6 @@ class tests extends CompilerTest { val failedUnderscore = List("-Ystop-before:collectEntryPoints") // #289 val failedOther = List("-Ystop-before:collectEntryPoints") // some non-obvious reason. need to look deeper - - - val twice = List("#runs", "2", "-YnoDoubleBindings") val allowDeepSubtypes = defaultOptions diff List("-Yno-deep-subtypes") From 74b2584e399cca21159dafe433abbef8c821d1fd Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 8 Jan 2015 11:13:21 +0100 Subject: [PATCH 14/72] Add deSkolemize method. We will need that at some point because type checking refined types will generate skolem variables for representing RefinedThis types if the lower type is not a singleton. --- .../tools/dotc/core/SymDenotations.scala | 2 +- src/dotty/tools/dotc/core/TypeOps.scala | 102 ++++++++++++++++++ 2 files changed, 103 insertions(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index 613d4b29cb76..c8fbb3d03977 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -600,7 +600,7 @@ object SymDenotations { isTerm && !is(MethodOrLazy, butNot = Label) && !isLocalDummy // def isOverridable: Boolean = !!! need to enforce that classes cannot be redefined - // def isSkolem: Boolean = ??? + def isSkolem: Boolean = name == nme.SKOLEM // ------ access to related symbols --------------------------------- diff --git a/src/dotty/tools/dotc/core/TypeOps.scala b/src/dotty/tools/dotc/core/TypeOps.scala index 88f495f47fe9..fb6ef6c0917b 100644 --- a/src/dotty/tools/dotc/core/TypeOps.scala +++ b/src/dotty/tools/dotc/core/TypeOps.scala @@ -86,6 +86,108 @@ trait TypeOps { this: Context => class SimplifyMap extends TypeMap { def apply(tp: Type) = simplify(tp, this) } + + /** Approximate a type `tp` with a type that does not contain skolem termrefs. + * @param toSuper if true, return the smallest supertype of `tp` with this property + * e;se return the largest subtype. + */ + final def deSkolemize(tp: Type, toSuper: Boolean): Type = + deSkolemize(tp, if (toSuper) 1 else -1, Set()) + + private def deSkolemize(tp: Type, variance: Int, seen: Set[Symbol]): Type = + traceIndented(s"deskolemize $tp, variance = $variance, seen = $seen = ") { + def approx(lo: Type = defn.NothingType, hi: Type = defn.AnyType, newSeen: Set[Symbol] = seen) = + if (variance == 0) NoType + else deSkolemize(if (variance < 0) lo else hi, variance, newSeen) + tp match { + case tp: NamedType => + val sym = tp.symbol + if (sym.isSkolem) + if (seen contains sym) NoType + else approx(hi = sym.info, newSeen = seen + sym) + else if (sym.isStatic) tp + else { + val pre1 = deSkolemize(tp.prefix, variance, seen) + if (pre1.exists && !pre1.isRef(defn.NothingClass)) tp.derivedSelect(pre1) + else { + ctx.log(s"deskolem: $tp: ${tp.info}") + tp.info match { + case TypeBounds(lo, hi) => approx(lo, hi) + case info => approx(defn.NothingType, info) + } + } + } + case _: ThisType | _: BoundType | _: SuperType | NoType | NoPrefix => + tp + case tp: RefinedType => + val parent1 = deSkolemize(tp.parent, variance, seen) + if (parent1.exists) { + val refinedInfo1 = deSkolemize(tp.refinedInfo, variance, seen) + if (refinedInfo1.exists) + tp.derivedRefinedType(parent1, tp.refinedName, refinedInfo1) + else + approx(hi = parent1) + } + else approx() + case tp: TypeAlias => + val alias1 = deSkolemize(tp.alias, variance * tp.variance, seen) + if (alias1.exists) tp.derivedTypeAlias(alias1) + else approx(hi = TypeBounds.empty) + case tp: TypeBounds => + val lo1 = deSkolemize(tp.lo, -variance, seen) + val hi1 = deSkolemize(tp.hi, variance, seen) + if (lo1.exists && hi1.exists) tp.derivedTypeBounds(lo1, hi1) + else approx(hi = + if (lo1.exists) TypeBounds.lower(lo1) + else if (hi1.exists) TypeBounds.upper(hi1) + else TypeBounds.empty) + case tp: ClassInfo => + val pre1 = deSkolemize(tp.prefix, variance, seen) + if (pre1.exists) tp.derivedClassInfo(pre1) + else NoType + case tp: AndOrType => + val tp1d = deSkolemize(tp.tp1, variance, seen) + val tp2d = deSkolemize(tp.tp2, variance, seen) + if (tp1d.exists && tp2d.exists) + tp.derivedAndOrType(tp1d, tp2d) + else if (tp.isAnd) + approx(hi = tp1d & tp2d) // if one of tp1d, tp2d exists, it is the result of tp1d & tp2d + else + approx(lo = tp1d & tp2d) + case tp: WildcardType => + val bounds1 = deSkolemize(tp.optBounds, variance, seen) + if (bounds1.exists) tp.derivedWildcardType(bounds1) + else WildcardType + case _ => + if (tp.isInstanceOf[MethodicType]) assert(variance != 0, tp) + deSkolemizeMap.mapOver(tp, variance, seen) + } + } + + private var deSkolemizeMapCache: DeSkolemizeMap = null + + private def deSkolemizeMap = { + if (deSkolemizeMapCache == null || deSkolemizeMapCache.definingCtx != this) + deSkolemizeMapCache = new DeSkolemizeMap + deSkolemizeMapCache + } + + private class DeSkolemizeMap(implicit ctx: Context) extends TypeMap { + def definingCtx = ctx + private var seen: Set[Symbol] = _ + def apply(tp: Type) = deSkolemize(tp, variance, seen) + def mapOver(tp: Type, variance: Int, seen: Set[Symbol]) = { + val savedVariance = this.variance + val savedSeen = this.seen + this.variance = variance + this.seen = seen + try super.mapOver(tp) + finally { + this.variance = savedVariance + this.seen = savedSeen + } + } + } /** Approximate union type by intersection of its dominators. * See Type#approximateUnion for an explanation. From 1309f0fab02337788fba656be19a432d80d59565 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 8 Jan 2015 11:27:58 +0100 Subject: [PATCH 15/72] Add transitive check for cycles in constraints. --- src/dotty/tools/dotc/config/Config.scala | 16 ++++++++- src/dotty/tools/dotc/core/Constraint.scala | 38 ++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/config/Config.scala b/src/dotty/tools/dotc/config/Config.scala index b17a5630f98a..0918dc20dce8 100644 --- a/src/dotty/tools/dotc/config/Config.scala +++ b/src/dotty/tools/dotc/config/Config.scala @@ -10,8 +10,22 @@ object Config { final val checkCacheMembersNamed = false + /** When updating a connstraint bound, check that the constrained parameter + * does not appear at the top-level of either of its bounds. + */ final val checkConstraintsNonCyclic = true - + + /** Like `checkConstraintsNonCyclic`, but all constrained parameters + * are tested for direct or indirect dependencies, each time a + * constraint is added in TypeComparer. + */ + final val checkConstraintsNonCyclicTrans = true + + /** Type comparer will fail with an assert if the upper bound + * of a constrained parameter becomes Nothing. This should be turned + * on only for specific debugging as normally instantiation to Nothing + * is not an error consdition. + */ final val flagInstantiationToNothing = false /** Enable noDoubleDef checking if option "-YnoDoubleDefs" is set. diff --git a/src/dotty/tools/dotc/core/Constraint.scala b/src/dotty/tools/dotc/core/Constraint.scala index e241794d0e4f..86d686d3731a 100644 --- a/src/dotty/tools/dotc/core/Constraint.scala +++ b/src/dotty/tools/dotc/core/Constraint.scala @@ -231,6 +231,44 @@ class Constraint(private val myMap: ParamInfo, for (pt <- domainPolys) checkNonCyclic(pt, myMap(pt)) } + /** Check that no constrained parameter contains itself as a bound, + * either directly or indirectly. This should be not a structer criterion + * than checkNonCyclic because transitivity should be eliminated always, + * but it's good to be paranoid. + */ + def checkNonCyclicTrans()(implicit ctx: Context): Unit = { + for (pt <- domainPolys) + checkNonCyclicTrans(pt, myMap(pt)) + } + + def checkNonCyclicTrans(pt: PolyType, entries: Array[Type])(implicit ctx: Context): Unit = + for ((entry, i) <- entries.zipWithIndex) { + def occursIn(params: Set[PolyParam], bound: Type, fromBelow: Boolean): Boolean = bound.stripTypeVar match { + case bound: PolyParam => + params.contains(bound) || { + at(bound) match { + case TypeBounds(lo, hi) => + occursIn(params + bound, if (fromBelow) lo else hi, fromBelow) + case _ => + false + } + } + case bound: AndOrType => + def occ1 = occursIn(params, bound.tp1, fromBelow) + def occ2 = occursIn(params, bound.tp2, fromBelow) + if (fromBelow == bound.isAnd) occ1 && occ2 else occ1 || occ2 + case _ => false + } + val param = PolyParam(pt, i) + entry match { + case TypeBounds(lo, hi) => + assert(!occursIn(Set(param), lo, fromBelow = true), s"$param occurs below $lo") + assert(!occursIn(Set(param), hi, fromBelow = false), s"$param occurs above $hi") + case _ => + } + } + + /** A new constraint which is derived from this constraint by updating * the entry for parameter `param` to `tpe`. * @pre `this contains param`. From 0a35baa8a1ed91a71398887ae7a1a08910e4faf0 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 8 Jan 2015 11:36:32 +0100 Subject: [PATCH 16/72] New scheme for subtyping refined types. - Instead of rebasing, use the DeBrujn level of a RefiendThis. - Make sure lower type is a singleton by skolemizing it if necessary. - Do the correct rebinding of the upper type's RefinedThis. Remarks: - The new scheme discovered quite a lot of errors which are mostly fixded in other commits of this branch. i0268 (GADT matching) still does not work, moved to pending. - Some optimizations are currently missing: (1) fast path refined subtyping (2) faster operations for substituting refined thistypes which explot the fact that RefinedThis is relatively rare. --- src/dotty/tools/dotc/core/TypeComparer.scala | 187 +++++++++++++------ src/dotty/tools/dotc/typer/Implicits.scala | 5 +- tests/{ => pending}/pos/i0268.scala | 0 3 files changed, 139 insertions(+), 53 deletions(-) rename tests/{ => pending}/pos/i0268.scala (100%) diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index 9807af9012cd..1a873c348d4f 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -25,6 +25,9 @@ class TypeComparer(initctx: Context) extends DotClass { private var pendingSubTypes: mutable.Set[(Type, Type)] = null private var recCount = 0 + var newScheme = true + var cmpSchemes = false + /** If the constraint is frozen we cannot add new bounds to the constraint. */ protected var frozenConstraint = false @@ -185,7 +188,7 @@ class TypeComparer(initctx: Context) extends DotClass { finally ignoreConstraint = saved val res = (param == bound) || (oldBounds eq newBounds) || updateConstraint(param, newBounds) - constr.println(s"added1 constraint $param ${if (fromBelow) ">:" else "<:"} $bound = $res") + //constr.println(s"added1 constraint $param ${if (fromBelow) ">:" else "<:"} $bound = $res") if (res) constr.println(constraint.show) res } @@ -210,33 +213,43 @@ class TypeComparer(initctx: Context) extends DotClass { */ def addConstraint(param: PolyParam, bound0: Type, fromBelow: Boolean): Boolean = { assert(!frozenConstraint) - val bound = bound0.dealias.stripTypeVar - def description = s"${param.show} ${if (fromBelow) ">:>" else "<:<"} ${bound.show} (${bound.getClass}) to ${constraint.show}" - constr.println(s"adding $description") - val res = bound match { - case bound: PolyParam if constraint contains bound => - val TypeBounds(lo, hi) = constraint.bounds(bound) - if (lo eq hi) - addConstraint(param, lo, fromBelow) - else if (param == bound) + def recur(bound0: Type): Boolean = { + assert(bound0.exists) + val bound = bound0.dealias.stripTypeVar + def description = s"${param.show} ${if (fromBelow) ">:>" else "<:<"} ${bound.show} (${bound.getClass}) to ${constraint.show}" + constr.println(s"adding $description") + constraint.bounds(param) match { + case TypeBounds(plo: PolyParam, phi) if constraint.contains(plo) && (plo eq phi) => + addConstraint(plo, bound, fromBelow) + case _ => + + val res = bound match { + case bound: PolyParam if constraint contains bound => + val TypeBounds(lo, hi) = constraint.bounds(bound) + if (lo eq hi) + addConstraint(param, lo, fromBelow) + else if (param == bound) + true + else if (fromBelow && param.occursIn(lo, fromBelow = true)) + unify(param, bound) + else if (!fromBelow && param.occursIn(hi, fromBelow = false)) + unify(bound, param) + else + addConstraint1(param, bound, fromBelow) && + addConstraint1(bound, param, !fromBelow) + case bound: AndOrType if fromBelow != bound.isAnd => + addConstraint(param, bound.tp1, fromBelow) && + addConstraint(param, bound.tp2, fromBelow) + case bound: WildcardType => true - else if (fromBelow && param.occursIn(lo, fromBelow = true)) - unify(param, bound) - else if (!fromBelow && param.occursIn(hi, fromBelow = false)) - unify(bound, param) - else - addConstraint1(param, bound, fromBelow) && - addConstraint1(bound, param, !fromBelow) - case bound: AndOrType if fromBelow != bound.isAnd => - addConstraint(param, bound.tp1, fromBelow) && - addConstraint(param, bound.tp2, fromBelow) - case bound: WildcardType => - true - case bound => // !!! remove to keep the originals - addConstraint1(param, bound, fromBelow) - } - constr.println(s"added $description = ${constraint.show}") - res + case bound => // !!! remove to keep the originals + addConstraint1(param, bound, fromBelow) + } + constr.println(s"added $description") + if (Config.checkConstraintsNonCyclicTrans) constraint.checkNonCyclicTrans() + res + }} + recur(bound0) // ctx.deSkolemize(bound0, toSuper = fromBelow)) } def isConstrained(param: PolyParam): Boolean = @@ -322,7 +335,8 @@ class TypeComparer(initctx: Context) extends DotClass { case _ => tp } } - tp.prefix match { + if (newScheme) tp + else tp.prefix match { case RefinedThis(rt, _) => rebaseFrom(rt) case pre: ThisType => rebaseFrom(pre.cls.info) case _ => tp @@ -350,7 +364,27 @@ class TypeComparer(initctx: Context) extends DotClass { if ((tp2 eq tp1) || (tp2 eq WildcardType) || (tp2 eq AnyType) && tp1.isValueType) return true - isSubType(tp1, tp2) + val saved = newScheme + try { + if (cmpSchemes) { + newScheme = false + val old = isSubType(tp1, tp2) + newScheme = true + val now = isSubType(tp1, tp2) + if (old != now) { + println(i"subtyping discrepancy for $tp1 <:< $tp2, was $old, now $now") + println(TypeComparer.explained(implicit ctx => { + ctx.typeComparer.newScheme = true + ctx.typeComparer.isSubType(tp1, tp2) + })) + println("================\nwhere previously:") + println(TypeComparer.explained(implicit ctx => ctx.typeComparer.isSubType(tp1, tp2))) + } + now + } + else isSubType(tp1, tp2) + } + finally newScheme = saved } protected def isSubTypeWhenFrozen(tp1: Type, tp2: Type): Boolean = { @@ -366,7 +400,7 @@ class TypeComparer(initctx: Context) extends DotClass { def isSubType(orig1: Type, orig2: Type): Boolean = { - def ctdSubType(tp1: Type, tp2: Type): Boolean = /*>|>*/ ctx.traceIndented(s"isSubType ${traceInfo(orig1, tp2)}, orig1 = ${orig1.show}, class2 = ${tp2.getClass}", subtyping) /*<|<*/ { + def ctdSubType(tp1: Type, tp2: Type): Boolean = /*>|>*/ ctx.traceIndented(s"isSubType ${traceInfo(tp1, tp2)}, class1 = ${tp1.getClass}, class2 = ${tp2.getClass}", subtyping) /*<|<*/ { if (tp2 eq NoType) false else if (tp1 eq tp2) true else { @@ -530,13 +564,13 @@ class TypeComparer(initctx: Context) extends DotClass { secondTryNamed(tp1, tp2) } case OrType(tp11, tp12) => - ctdSubType(tp11, tp2) && ctdSubType(tp12, tp2) + isSubType(tp11, tp2) && isSubType(tp12, tp2) case tp1: PolyParam => def comparePolyParam = tp1 == tp2 || { - if (solvedConstraint && (constraint contains tp1)) ctdSubType(bounds(tp1).lo, tp2) + if (solvedConstraint && (constraint contains tp1)) isSubType(bounds(tp1).lo, tp2) else - ctdSubTypeWhenFrozen(bounds(tp1).hi, tp2) || { + isSubTypeWhenFrozen(bounds(tp1).hi, tp2) || { if (isConstrained(tp1)) addConstraint(tp1, tp2, fromBelow = false) && { if ((!frozenConstraint) && @@ -554,23 +588,25 @@ class TypeComparer(initctx: Context) extends DotClass { comparePolyParam case tp1: RefinedThis => tp2 match { - case tp2: RefinedThis if tp1.binder.parent =:= tp2.binder.parent => true - case _ => thirdTry(tp1, tp2) + case tp2: RefinedThis + if (if (newScheme) tp1.level == tp2.level + else tp1.binder.parent =:= tp2.binder.parent) => true + case _ => thirdTry(tp1, tp2) } case tp1: BoundType => tp1 == tp2 || thirdTry(tp1, tp2) case tp1: TypeVar => - (tp1 eq tp2) || ctdSubType(tp1.underlying, tp2) + (tp1 eq tp2) || isSubType(tp1.underlying, tp2) case tp1: WildcardType => def compareWild = tp1.optBounds match { - case TypeBounds(lo, _) => ctdSubType(lo, tp2) + case TypeBounds(lo, _) => isSubType(lo, tp2) case _ => true } compareWild case tp1: LazyRef => - ctdSubType(tp1.ref, tp2) + isSubType(tp1.ref, tp2) case tp1: AnnotatedType => - ctdSubType(tp1.tpe, tp2) + isSubType(tp1.tpe, tp2) case ErrorType => true case _ => @@ -604,9 +640,8 @@ class TypeComparer(initctx: Context) extends DotClass { val gbounds1 = ctx.gadt.bounds(tp1.symbol) if (gbounds1 != null) ctdSubTypeWhenFrozen(gbounds1.hi, tp2) || - (ctx.mode is Mode.GADTflexible) && - narrowGADTBounds(tp1, TypeBounds(gbounds1.lo, gbounds1.hi & tp2)) || - tryRebase2nd + narrowGADTBounds(tp1, tp2, fromBelow = false) || + tryRebase2nd else if (lo1 eq hi1) ctdSubType(hi1, tp2) else tryRebase2nd case _ => @@ -626,9 +661,8 @@ class TypeComparer(initctx: Context) extends DotClass { val gbounds2 = ctx.gadt.bounds(tp2.symbol) if (gbounds2 != null) ctdSubTypeWhenFrozen(tp1, gbounds2.lo) || - (ctx.mode is Mode.GADTflexible) && - narrowGADTBounds(tp2, TypeBounds(gbounds2.lo | tp1, gbounds2.hi)) || - fourthTry(tp1, tp2) + narrowGADTBounds(tp2, tp1, fromBelow = true) || + fourthTry(tp1, tp2) else ((frozenConstraint || !isCappable(tp1)) && ctdSubType(tp1, lo2) || tryRebase3rd) @@ -651,7 +685,7 @@ class TypeComparer(initctx: Context) extends DotClass { } def compareRefinedSlow: Boolean = { def hasMatchingMember(name: Name): Boolean = /*>|>*/ ctx.traceIndented(s"hasMatchingMember($name) ${tp1.member(name).info.show}", subtyping) /*<|<*/ { - val tp1r = rebaseQual(tp1, name) + val tp1r = rebaseQual(tp1, name) //val old = memberMatches(narrowRefined(tp1r) member name) //val now = memberMatches(orig1.ensureSingleton member name) //if (old != now) @@ -679,9 +713,20 @@ class TypeComparer(initctx: Context) extends DotClass { name2 == nme.WILDCARD || hasMatchingMember(name2) || fourthTry(tp1, tp2)) - || needsEtaLift(tp1, tp2) && tp1.testLifted(tp2.typeParams, ctdSubType(_, tp2))) + || needsEtaLift(tp1, tp2) && tp1.testLifted(tp2.typeParams, ctdSubType(_, tp2))) } - def compareRefined: Boolean = tp1.widen match { + def compareRefined: Boolean = + if (newScheme) { + val normalPath = + ctdSubType(tp1, parent2) && ( + name2 == nme.WILDCARD + || hasMatchingMember(name2, tp1, tp2) + || fourthTry(tp1, tp2) + ) + normalPath || + needsEtaLift(tp1, tp2) && tp1.testLifted(tp2.typeParams, isSubType(_, tp2)) + } + else tp1.widen match { case tp1 @ RefinedType(parent1, name1) if name1 == name2 && name1.isTypeName => normalizedInfo(tp1) match { case bounds1: TypeAlias => @@ -892,6 +937,26 @@ class TypeComparer(initctx: Context) extends DotClass { ctdSubType(orig1, orig2) } + def hasMatchingMember(name: Name, orig1: Type, tp2: RefinedType): Boolean = /*>|>*/ ctx.traceIndented(s"hasMatchingMember($orig1 . $name, ${tp2.refinedInfo}) ${orig1.member(name).info.show}", subtyping) /*<|<*/ { + val base = orig1.ensureSingleton + var rinfo2 = tp2.refinedInfo.substRefinedThis(0, base) + def qualifies(m: SingleDenotation) = isSubType(m.info, rinfo2) + def memberMatches(mbr: Denotation): Boolean = mbr match { // inlined hasAltWith for performance + case mbr: SingleDenotation => qualifies(mbr) + case _ => mbr hasAltWith qualifies + } + memberMatches(base member name) || + orig1.isInstanceOf[SingletonType] && + { // special case for situations like: + // foo <: C { type T = foo.T } + rinfo2 match { + case rinfo2: TypeAlias => + !ctx.phase.erasedTypes && (base select name) =:= rinfo2.alias + case _ => false + } + } + } + /** 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, AndTypes or AnnotatedTypes. @@ -931,15 +996,28 @@ class TypeComparer(initctx: Context) extends DotClass { } /** Does `tp` need to be eta lifted to be comparable to `target`? */ - def needsEtaLift(tp: Type, target: RefinedType) = { + def needsEtaLift(tp: Type, target: RefinedType): Boolean = { + //default.echo(i"needs eta $tp $target?", { val name = target.refinedName (name.isLambdaArgName || (name eq tpnme.Apply)) && target.isLambda && tp.exists && !tp.isLambda + //}) } - def narrowGADTBounds(tr: NamedType, bounds: TypeBounds): Boolean = - isSubType(bounds.lo, bounds.hi) && - { ctx.gadt.setBounds(tr.symbol, bounds); true } + def narrowGADTBounds(tr: NamedType, bound: Type, fromBelow: Boolean): Boolean = + ctx.mode.is(Mode.GADTflexible) && { + val tparam = tr.symbol + val bound1 = bound // ctx.deSkolemize(bound, toSuper = fromBelow) + println(s"narrow gadt bound of $tparam: ${tparam.info} from ${if (fromBelow) "below" else "above"} to $bound1 ${bound1.isRef(tparam)}") + !bound1.isRef(tparam) && { + val oldBounds = ctx.gadt.bounds(tparam) + val newBounds = + if (fromBelow) TypeBounds(oldBounds.lo | bound1, oldBounds.hi) + else TypeBounds(oldBounds.lo, oldBounds.hi & bound1) + isSubType(newBounds.lo, newBounds.hi) && + { ctx.gadt.setBounds(tparam, newBounds); true } + } + } // Tests around `matches` @@ -1484,6 +1562,11 @@ class ExplainingTypeComparer(initctx: Context) extends TypeComparer(initctx) { super.isSubType(tp1, tp2) } + override def hasMatchingMember(name: Name, orig1: Type, tp2: RefinedType): Boolean = + traceIndented(s"hasMatchingMember(${show(orig1)} . $name, ${show(tp2.refinedInfo)}), member = ${show(orig1.member(name).info)}") { + super.hasMatchingMember(name, orig1, tp2) + } + override def lub(tp1: Type, tp2: Type) = traceIndented(s"lub(${show(tp1)}, ${show(tp2)})") { super.lub(tp1, tp2) diff --git a/src/dotty/tools/dotc/typer/Implicits.scala b/src/dotty/tools/dotc/typer/Implicits.scala index 82424164ac37..93043610e076 100644 --- a/src/dotty/tools/dotc/typer/Implicits.scala +++ b/src/dotty/tools/dotc/typer/Implicits.scala @@ -450,7 +450,10 @@ trait Implicits { self: Typer => private def nestedContext = ctx.fresh.setMode(ctx.mode &~ Mode.ImplicitsEnabled) private def implicitProto(resultType: Type, f: Type => Type) = - if (argument.isEmpty) f(resultType) else ViewProto(f(argument.tpe.widen), f(resultType)) + if (argument.isEmpty) f(resultType) else ViewProto(f(argument.tpe/*.widen*/), f(resultType)) + // !!! TODO: check performance implications + // If we do the widen, SyntheticMethods, line 66 fails to compile + // val synthetic = sym.copy(...) assert(argument.isEmpty || argument.tpe.isValueType || argument.tpe.isInstanceOf[ExprType], d"found: ${argument.tpe}, expected: $pt") diff --git a/tests/pos/i0268.scala b/tests/pending/pos/i0268.scala similarity index 100% rename from tests/pos/i0268.scala rename to tests/pending/pos/i0268.scala From 979ff5e0dbffc66fd008be0544690dbd194fe066 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 8 Jan 2015 11:40:59 +0100 Subject: [PATCH 17/72] Simplify and fix bounds propagation in constraints. The previous scheme did not ensure that transitivity was eliminated. Example scenario: We have in the constraint P <: Q for constrained variables P, Q and add Q <: T Previous propagation added the constraint and then tested whether the bounds of all variables were satisfiable. For Q we test P <: T but that is true because P <: Q and we already added the constraint Q <: T. So we fail to add the constraint P <: T, and transitivity is no longer eliminated. Instead we now test the new bounds (in this case P <: T) *before* adding the new constraint Q <: T. This is also simpler than the previous scheme. --- src/dotty/tools/dotc/core/TypeComparer.scala | 263 ++++++++++--------- test/dotc/tests.scala | 2 +- tests/{ => pending}/pos/t0674.scala | 5 +- 3 files changed, 150 insertions(+), 120 deletions(-) rename tests/{ => pending}/pos/t0674.scala (95%) diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index 1a873c348d4f..25c53e39c1a5 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -43,6 +43,14 @@ class TypeComparer(initctx: Context) extends DotClass { protected var solvedConstraint = false private var needsGc = false + + /** Bounds for constrained parameters yet to be added to the constraint */ + private var pendingBoundss: SimpleMap[PolyParam, TypeBounds] = SimpleMap.Empty + + /** If `param` is in `needsSatCheck` then the constraint should be checked + * for satisfiability after `param`'s bound are updated. + */ + private var needsSatCheck: Set[PolyParam] = Set() /** Is a subtype check in course? In that case we may not * permanently instantiate type variables, because the corresponding @@ -89,7 +97,30 @@ class TypeComparer(initctx: Context) extends DotClass { myAnyType } - // Constraint handling + /* Constraint handling: + * + * Constraints are required to be in normalized form. This means + * (1) if P <: Q in C then also Q >: P in C + * (2) if P r Q in C and Q r R in C then also P r R in C, where r is <: or :> + * + * "P <: Q in C" means here: There is a constraint P <: H[Q], + * where H is the multi-hole context given by: + * + * H = [] + * H & T + * T & H + * H | H + * + * (the idea is that a parameter Q in a H context is guaranteed to be a supertype of P). + * + * "P >: Q in C" means: There is a constraint P >: L[Q], + * where L is the multi-hole context given by: + * + * L = [] + * L | T + * T | L + * L & L + */ /** Map that approximates each param in constraint by its lower bound. * Currently only used for diagnostics. @@ -103,96 +134,24 @@ class TypeComparer(initctx: Context) extends DotClass { } } - /** If `param` is contained in constraint, test whether its - * bounds are non-empty. Otherwise return `true`. - */ - private def checkBounds(param: PolyParam): Boolean = constraint.at(param) match { - case TypeBounds(lo, hi) => - if (Stats.monitored) Stats.record("checkBounds") - isSubType(lo, hi) - case _ => true - } - - /** Test validity of constraint for parameter `changed` and of all - * parameters that depend on it. - */ - private def propagate(changed: PolyParam): Boolean = - if (Config.trackConstrDeps) - checkBounds(changed) && - propagate(constraint.dependentParams(changed) - changed, Set(changed)) - else - constraint forallParams checkBounds - - /** Ensure validity of constraints for parameters `params` and of all - * parameters that depend on them and that have not been tested - * now or before. If `trackConstrDeps` is not set, do this for all - * parameters in the constraint. - * @param seen the set of parameters that have been tested before. - */ - private def propagate(params: Set[PolyParam], seen: Set[PolyParam]): Boolean = - params.isEmpty || - (params forall checkBounds) && { - val seen1 = seen ++ params - val nextParams = (Set[PolyParam]() /: params) { (ps, p) => - ps ++ (constraint.dependentParams(p) -- seen1) - } - propagate(nextParams, seen1) - } - - /** Check whether the lower bounds of all parameters in this + /** Test whether the lower bounds of all parameters in this * constraint are a solution to the constraint. - * As an optimization, when `trackConstrDeps` is set, we - * only test that the solutions satisfy the constraints `changed` - * and all parameters that depend on it. */ - def isSatisfiable(changed: PolyParam): Boolean = { + def isSatisfiable: Boolean = { val saved = solvedConstraint solvedConstraint = true try - if (Config.trackConstrDeps) propagate(changed) - else - constraint.forallParams { param => - checkBounds(param) || { - val TypeBounds(lo, hi) = constraint.bounds(param) - ctx.log(i"sub fail $lo <:< $hi") - ctx.log(i"approximated = ${approxParams(lo)} <:< ${approxParams(hi)}") - false - } + constraint.forallParams { param => + val TypeBounds(lo, hi) = constraint.at(param) + isSubType(lo, hi) || { + ctx.log(i"sub fail $lo <:< $hi") + ctx.log(i"approximated = ${approxParams(lo)} <:< ${approxParams(hi)}") + false } + } finally solvedConstraint = saved } - /** Update constraint for `param` to `bounds`, check that - * new constraint is still satisfiable. - */ - private def updateConstraint(param: PolyParam, bounds: TypeBounds): Boolean = { - val saved = constraint - constraint = constraint.updated(param, bounds) - if (propagate(param)) { - if (isSatisfiable(param)) return true - ctx.log(i"SAT $constraint produced by $param $bounds is not satisfiable") - } - constraint = saved // don't leave the constraint in unsatisfiable state - false - } - - private def addConstraint1(param: PolyParam, bound: Type, fromBelow: Boolean): Boolean = { - val oldBounds = constraint.bounds(param) - assert(!bound.isInstanceOf[TypeVar]) - val saved = ignoreConstraint - ignoreConstraint = true - val newBounds = - try - if (fromBelow) oldBounds.derivedTypeBounds(oldBounds.lo | bound, oldBounds.hi) - else oldBounds.derivedTypeBounds(oldBounds.lo, oldBounds.hi & bound) - finally ignoreConstraint = saved - val res = - (param == bound) || (oldBounds eq newBounds) || updateConstraint(param, newBounds) - //constr.println(s"added1 constraint $param ${if (fromBelow) ">:" else "<:"} $bound = $res") - if (res) constr.println(constraint.show) - res - } - /** Make p2 = p1, transfer all bounds of p2 to p1 */ private def unify(p1: PolyParam, p2: PolyParam): Boolean = { constr.println(s"unifying $p1 $p2") @@ -200,7 +159,7 @@ class TypeComparer(initctx: Context) extends DotClass { val bounds = constraint1.bounds(p1) isSubType(bounds.lo, bounds.hi) && { constraint = constraint1; true } } - + /** If current constraint set is not frozen, add the constraint * * param >: bound if fromBelow is true @@ -212,44 +171,114 @@ class TypeComparer(initctx: Context) extends DotClass { * @return Whether the augmented constraint is still satisfiable. */ def addConstraint(param: PolyParam, bound0: Type, fromBelow: Boolean): Boolean = { - assert(!frozenConstraint) - def recur(bound0: Type): Boolean = { - assert(bound0.exists) - val bound = bound0.dealias.stripTypeVar - def description = s"${param.show} ${if (fromBelow) ">:>" else "<:<"} ${bound.show} (${bound.getClass}) to ${constraint.show}" - constr.println(s"adding $description") + + /** Add bidirectional constraint. If new constraint implies 'A <: B' we also + * make sure 'B >: A' gets added and vice versa. Furthermore, if the constraint + * implies 'A <: B <: A', A and B get unified. + */ + def addBi(param: PolyParam, bound: Type, fromBelow: Boolean): Boolean = constraint.bounds(param) match { - case TypeBounds(plo: PolyParam, phi) if constraint.contains(plo) && (plo eq phi) => - addConstraint(plo, bound, fromBelow) + case TypeBounds(plo: PolyParam, phi) if (plo eq phi) && constraint.contains(plo) => + addBi(plo, bound, fromBelow) case _ => - - val res = bound match { - case bound: PolyParam if constraint contains bound => - val TypeBounds(lo, hi) = constraint.bounds(bound) - if (lo eq hi) - addConstraint(param, lo, fromBelow) - else if (param == bound) - true - else if (fromBelow && param.occursIn(lo, fromBelow = true)) - unify(param, bound) - else if (!fromBelow && param.occursIn(hi, fromBelow = false)) - unify(bound, param) - else - addConstraint1(param, bound, fromBelow) && - addConstraint1(bound, param, !fromBelow) - case bound: AndOrType if fromBelow != bound.isAnd => - addConstraint(param, bound.tp1, fromBelow) && - addConstraint(param, bound.tp2, fromBelow) - case bound: WildcardType => + bound match { + case bound: PolyParam if constraint contains bound => + val TypeBounds(lo, hi) = constraint.bounds(bound) + if (lo eq hi) + addBi(param, lo, fromBelow) + else if (param == bound) + true + else if (fromBelow && param.occursIn(lo, fromBelow = true)) + unify(param, bound) + else if (!fromBelow && param.occursIn(hi, fromBelow = false)) + unify(bound, param) + else + addUni(param, bound, fromBelow) && + addUni(bound, param, !fromBelow) + case bound: AndOrType if fromBelow != bound.isAnd => + addBi(param, bound.tp1, fromBelow) && + addBi(param, bound.tp2, fromBelow) + case bound: WildcardType => + true + case bound => // !!! remove to keep the originals + addUni(param, bound, fromBelow) + } + } + + /** Add constraint without propagating in the other direction or unifying */ + def addUni(param: PolyParam, bound: Type, fromBelow: Boolean): Boolean = { + val oldBounds = constraint.bounds(param) + def narrowedBounds(bounds: TypeBounds): TypeBounds = { + val savedIgnore = ignoreConstraint + val savedFrozen = frozenConstraint + ignoreConstraint = true + frozenConstraint = true + try + if (fromBelow) bounds.derivedTypeBounds(bounds.lo | bound, bounds.hi) + else bounds.derivedTypeBounds(bounds.lo, bounds.hi & bound) + finally { + ignoreConstraint = savedIgnore + frozenConstraint = savedFrozen + } + } + val newBounds = narrowedBounds(oldBounds) + (param == bound) || + (oldBounds eq newBounds) || + { val pendingBounds = pendingBoundss(param) + if (pendingBounds == null) + // Why the pendingBoundss tests? It is possible that recursive subtype invocations + // come back to param instead. An example came up when compiling ElimRepeated where + // we got the constraint + // + // Coll <: IterableLike[Tree, Coll] + // + // and added + // + // List[Tree] <: Coll + // + // The recursive bounds test is then + // + // List[Tree] <: IterableLike[Tree, Coll] + // + // and because of the F-bounded polymorphism in the supertype of List, + // i.e. List[T] <: IterableLike[T, List[T]], this leads again to + // + // List[Tree] <: Coll + try { + pendingBoundss = pendingBoundss.updated(param, newBounds) + isSubType(newBounds.lo, newBounds.hi) && { + constraint = constraint.updated(param, pendingBoundss(param)) + if (needsSatCheck(param)) { + assert(isSatisfiable) + needsSatCheck -= param + } + true + } + } + finally pendingBoundss = pendingBoundss.remove(param) + else { + // TODO: investigate - if the last line in this comment is uncommented, we get a cyclic + // constraint error in tools/io. For now, we do without even though this + // risks allowing unsatisfiable constraints to get through. + // Unsatisfiable constraints are caught by an assertion that is executed later + // in case we got here. + //pendingBoundss = pendingBoundss.updated(param, narrowedBounds(pendingBounds)) + + needsSatCheck += param true - case bound => // !!! remove to keep the originals - addConstraint1(param, bound, fromBelow) + } } - constr.println(s"added $description") - if (Config.checkConstraintsNonCyclicTrans) constraint.checkNonCyclicTrans() - res - }} - recur(bound0) // ctx.deSkolemize(bound0, toSuper = fromBelow)) + } + + val bound = (if (false) ctx.deSkolemize(bound0, toSuper = fromBelow) else bound0) + .dealias.stripTypeVar + def description = s"${param.show} ${if (fromBelow) ">:>" else "<:<"} ${bound.show} to ${constraint.show}" + constr.println(s"adding $description") + val res = addBi(param, bound, fromBelow) + constr.println(s"added $description") + if (Config.checkConstraintsNonCyclicTrans) constraint.checkNonCyclicTrans() + if (needsSatCheck(param)) assert(isSatisfiable) + res } def isConstrained(param: PolyParam): Boolean = diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index e12becd0c740..71668823a3ce 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -126,7 +126,7 @@ class tests extends CompilerTest { @Test def dotc_core_pickling = compileDir(dotcDir + "tools/dotc/core/pickling", failedOther)(allowDeepSubtypes) // Cannot emit primitive conversion from V to Z - @Test def dotc_transform = compileDir(dotcDir + "tools/dotc/transform", failedbyName)(allowDeepSubtypes) + @Test def dotc_transform = compileDir(dotcDir + "tools/dotc/transform", failedbyName) @Test def dotc_parsing = compileDir(dotcDir + "tools/dotc/parsing", failedOther) // Expected primitive types I - Ljava/lang/Object diff --git a/tests/pos/t0674.scala b/tests/pending/pos/t0674.scala similarity index 95% rename from tests/pos/t0674.scala rename to tests/pending/pos/t0674.scala index 589eeec9f322..e405f4b8d2e2 100644 --- a/tests/pos/t0674.scala +++ b/tests/pending/pos/t0674.scala @@ -11,7 +11,8 @@ for(a <- Some(1); i <- Some(9); j <- Some(10); k <- Some(11); - l <- Some(12); + l <- Some(12) +/* m <- Some(13); n <- Some(14); o <- Some(15); @@ -39,7 +40,7 @@ for(a <- Some(1); l <- Some(12); m <- Some(13); n <- Some(14); - o <- Some(15) + o <- Some(15)*/ // p <- Some(16); // q <- Some(17) // r <- Some(18); From 0bc9f680af7670985ff10b1e5fa472655745c90b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 8 Jan 2015 11:56:42 +0100 Subject: [PATCH 18/72] Remove choice between new and old scheme for subtyping refined types. Now that new scheme works we can drop the alternative. --- src/dotty/tools/dotc/config/Config.scala | 2 +- src/dotty/tools/dotc/core/TypeComparer.scala | 204 ++----------------- 2 files changed, 17 insertions(+), 189 deletions(-) diff --git a/src/dotty/tools/dotc/config/Config.scala b/src/dotty/tools/dotc/config/Config.scala index 0918dc20dce8..e1657a273ce5 100644 --- a/src/dotty/tools/dotc/config/Config.scala +++ b/src/dotty/tools/dotc/config/Config.scala @@ -19,7 +19,7 @@ object Config { * are tested for direct or indirect dependencies, each time a * constraint is added in TypeComparer. */ - final val checkConstraintsNonCyclicTrans = true + final val checkConstraintsNonCyclicTrans = false /** Type comparer will fail with an assert if the upper bound * of a constrained parameter becomes Nothing. This should be turned diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index 25c53e39c1a5..98a0d665c2d1 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -25,9 +25,6 @@ class TypeComparer(initctx: Context) extends DotClass { private var pendingSubTypes: mutable.Set[(Type, Type)] = null private var recCount = 0 - var newScheme = true - var cmpSchemes = false - /** If the constraint is frozen we cannot add new bounds to the constraint. */ protected var frozenConstraint = false @@ -227,8 +224,8 @@ class TypeComparer(initctx: Context) extends DotClass { { val pendingBounds = pendingBoundss(param) if (pendingBounds == null) // Why the pendingBoundss tests? It is possible that recursive subtype invocations - // come back to param instead. An example came up when compiling ElimRepeated where - // we got the constraint + // come back with another constraint for `param`. An example came up when compiling + // ElimRepeated where we got the constraint // // Coll <: IterableLike[Tree, Coll] // @@ -310,82 +307,6 @@ class TypeComparer(initctx: Context) extends DotClass { inst } - // Keeping track of seen refinements - - /** A map from refined names to the refined types in which they occur. - * During the subtype check involving the parent of a refined type, - * the refined name is stored in the map, so that the outermost - * refinements can be retrieved when interpreting a reference to the name. - * The name is associated with a pair of refinements. If the refinedInfo is - * skipped in sub- and super-type at the same time (first clause of - * `compareRefined`, both refinements are stored. If the name only appears - * as a refinement in the sub- or -super-type, the refinement type is stored - * twice as both elements of the pair. - */ - protected var pendingRefinedBases: SimpleMap[Name, Set[(RefinedType, RefinedType)]] - = SimpleMap.Empty - - /** Add pending name to `pendingRefinedBases`. */ - private def addPendingName(name: Name, rt1: RefinedType, rt2: RefinedType) = { - var s = pendingRefinedBases(name) - if (s == null) s = Set() - pendingRefinedBases = pendingRefinedBases.updated(name, s + ((rt1, rt2))) - } - - /** Given a selection of qualifier `qual` with given `name`, return a refined type - * that refines `qual`, or if that fails return `qual` itself. - * @param considerBoth If true consider both lower and upper base of `name` when - * checking for refinement (but always return the lower one) - * @see Type#refines - */ - private def rebaseQual(qual: Type, name: Name, considerBoth: Boolean = false): Type = { - val bases = pendingRefinedBases(name) - if (bases == null) qual - else bases.find { - case (tp1, tp2) => - (tp1 refines qual) || considerBoth && (tp1 ne tp2) && (tp2 refines qual) - } match { - case Some((base1, _)) => base1 - case _ => qual - } - } - - /** If the prefix of a named type is `this` (i.e. an instance of type - * `ThisType` or `RefinedThis`), and there is a refinement type R that - * "refines" (transitively contains as its parent) a class reference - * or refinement corresponding to the prefix, return the named type where - * the prefix is replaced by `RefinedThis(R)`. Otherwise return the named type itself. - */ - private def rebase(tp: NamedType): Type = { - def rebaseFrom(prefix: Type): Type = { - rebaseQual(prefix, tp.name, considerBoth = true) match { - case rt: RefinedType if rt ne prefix => - tp.derivedSelect(RefinedThis(rt, 0)).dealias // dealias to short-circuit cycles spanning type aliases or LazyRefs - case _ => tp - } - } - if (newScheme) tp - else tp.prefix match { - case RefinedThis(rt, _) => rebaseFrom(rt) - case pre: ThisType => rebaseFrom(pre.cls.info) - case _ => tp - } - } - - /** If the given refined type is refined further, return the member - * of the refiend name relative to the refining base, otherwise return - * `refinedInfo`. - * TODO: Figure out why cannot simply write - * - * rebaseQual(rt, rt.refinedName).member(rt.refinedName).info - * - * (too much forcing, probably). - */ - def normalizedInfo(rt: RefinedType) = { - val base = rebaseQual(rt, rt.refinedName) - if (base eq rt) rt.refinedInfo else base.member(rt.refinedName).info - } - // Subtype testing `<:<` def topLevelSubType(tp1: Type, tp2: Type): Boolean = { @@ -393,27 +314,7 @@ class TypeComparer(initctx: Context) extends DotClass { if ((tp2 eq tp1) || (tp2 eq WildcardType) || (tp2 eq AnyType) && tp1.isValueType) return true - val saved = newScheme - try { - if (cmpSchemes) { - newScheme = false - val old = isSubType(tp1, tp2) - newScheme = true - val now = isSubType(tp1, tp2) - if (old != now) { - println(i"subtyping discrepancy for $tp1 <:< $tp2, was $old, now $now") - println(TypeComparer.explained(implicit ctx => { - ctx.typeComparer.newScheme = true - ctx.typeComparer.isSubType(tp1, tp2) - })) - println("================\nwhere previously:") - println(TypeComparer.explained(implicit ctx => ctx.typeComparer.isSubType(tp1, tp2))) - } - now - } - else isSubType(tp1, tp2) - } - finally newScheme = saved + isSubType(tp1, tp2) } protected def isSubTypeWhenFrozen(tp1: Type, tp2: Type): Boolean = { @@ -617,9 +518,7 @@ class TypeComparer(initctx: Context) extends DotClass { comparePolyParam case tp1: RefinedThis => tp2 match { - case tp2: RefinedThis - if (if (newScheme) tp1.level == tp2.level - else tp1.binder.parent =:= tp2.binder.parent) => true + case tp2: RefinedThis if tp1.level == tp2.level => true case _ => thirdTry(tp1, tp2) } case tp1: BoundType => @@ -643,11 +542,6 @@ class TypeComparer(initctx: Context) extends DotClass { } def secondTryNamed(tp1: NamedType, tp2: Type): Boolean = { - def tryRebase2nd = { - val tp1rebased = rebase(tp1) - if (tp1rebased ne tp1) ctdSubType(tp1rebased, tp2) - else thirdTry(tp1, tp2) - } tp1.info match { // There was the following code, which was meant to implement this logic: // If x has type A | B, then x.type <: C if @@ -670,21 +564,16 @@ class TypeComparer(initctx: Context) extends DotClass { if (gbounds1 != null) ctdSubTypeWhenFrozen(gbounds1.hi, tp2) || narrowGADTBounds(tp1, tp2, fromBelow = false) || - tryRebase2nd + thirdTry(tp1, tp2) else if (lo1 eq hi1) ctdSubType(hi1, tp2) - else tryRebase2nd + else thirdTry(tp1, tp2) case _ => - tryRebase2nd + thirdTry(tp1, tp2) } } def thirdTry(tp1: Type, tp2: Type): Boolean = tp2 match { case tp2: NamedType => - def tryRebase3rd = { - val tp2rebased = rebase(tp2) - if (tp2rebased ne tp2) ctdSubType(tp1, tp2rebased) - else fourthTry(tp1, tp2) - } def compareNamed: Boolean = tp2.info match { case TypeBounds(lo2, hi2) => val gbounds2 = ctx.gadt.bounds(tp2.symbol) @@ -694,7 +583,7 @@ class TypeComparer(initctx: Context) extends DotClass { fourthTry(tp1, tp2) else ((frozenConstraint || !isCappable(tp1)) && ctdSubType(tp1, lo2) - || tryRebase3rd) + || fourthTry(tp1, tp2)) case _ => val cls2 = tp2.symbol @@ -703,74 +592,18 @@ class TypeComparer(initctx: Context) extends DotClass { if (base.exists && (base ne tp1)) return ctdSubType(base, tp2) if (cls2 == defn.SingletonClass && tp1.isStable) return true } - tryRebase3rd + fourthTry(tp1, tp2) } compareNamed case tp2 @ RefinedType(parent2, name2) => - def qualifies(m: SingleDenotation) = isSubType(m.info, tp2.refinedInfo) - def memberMatches(mbr: Denotation): Boolean = mbr match { // inlined hasAltWith for performance - case mbr: SingleDenotation => qualifies(mbr) - case _ => mbr hasAltWith qualifies - } - def compareRefinedSlow: Boolean = { - def hasMatchingMember(name: Name): Boolean = /*>|>*/ ctx.traceIndented(s"hasMatchingMember($name) ${tp1.member(name).info.show}", subtyping) /*<|<*/ { - val tp1r = rebaseQual(tp1, name) - //val old = memberMatches(narrowRefined(tp1r) member name) - //val now = memberMatches(orig1.ensureSingleton member name) - //if (old != now) - // assert(false, i"discrepancy: $tp1 <:< $tp2, narrowRefined = ${narrowRefined(tp1r)} -> $old, ensureSingle = ${orig1.ensureSingleton}: $orig1 -> $now") - - (memberMatches(narrowRefined(tp1r) member name) - || - { // special case for situations like: - // foo <: C { type T = foo.T } - tp2.refinedInfo match { - case rinfo: TypeAlias => - !ctx.phase.erasedTypes && (tp1r select name) =:= rinfo.alias - case _ => false - } - }) - } - val matchesParent = { - val saved = pendingRefinedBases - try { - addPendingName(name2, tp2, tp2) - ctdSubType(tp1, parent2) - } finally pendingRefinedBases = saved - } - (matchesParent && ( + def compareRefined: Boolean = { + val normalPath = + ctdSubType(tp1, parent2) && ( name2 == nme.WILDCARD - || hasMatchingMember(name2) - || fourthTry(tp1, tp2)) - || needsEtaLift(tp1, tp2) && tp1.testLifted(tp2.typeParams, ctdSubType(_, tp2))) - } - def compareRefined: Boolean = - if (newScheme) { - val normalPath = - ctdSubType(tp1, parent2) && ( - name2 == nme.WILDCARD || hasMatchingMember(name2, tp1, tp2) - || fourthTry(tp1, tp2) - ) - normalPath || + || fourthTry(tp1, tp2)) + normalPath || needsEtaLift(tp1, tp2) && tp1.testLifted(tp2.typeParams, isSubType(_, tp2)) - } - else tp1.widen match { - case tp1 @ RefinedType(parent1, name1) if name1 == name2 && name1.isTypeName => - normalizedInfo(tp1) match { - case bounds1: TypeAlias => - isSubType(bounds1, tp2.refinedInfo) && { - val saved = pendingRefinedBases - try { - addPendingName(name1, tp1, tp2) - ctdSubType(parent1, parent2) - } finally pendingRefinedBases = saved - } - case _ => - compareRefinedSlow - } - case _ => - compareRefinedSlow } compareRefined case OrType(tp21, tp22) => @@ -873,13 +706,8 @@ class TypeComparer(initctx: Context) extends DotClass { } } case tp1: RefinedType => - { val saved = pendingRefinedBases - try { - addPendingName(tp1.refinedName, tp1, tp1) - isNewSubType(tp1.parent, tp2) - } - finally pendingRefinedBases = saved - } || needsEtaLift(tp2, tp1) && tp2.testLifted(tp1.typeParams, ctdSubType(tp1, _)) + isNewSubType(tp1.parent, tp2) || + needsEtaLift(tp2, tp1) && tp2.testLifted(tp1.typeParams, ctdSubType(tp1, _)) case AndType(tp11, tp12) => eitherIsSubType(tp11, tp2, tp12, tp2) case JavaArrayType(elem1) => From 9f745338242524c6607fa7b2930157b0c71be939 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 8 Jan 2015 17:52:00 +0100 Subject: [PATCH 19/72] Refacttored skolemization logic ... into a new trait "Skolemization". --- src/dotty/tools/dotc/core/NameOps.scala | 2 +- src/dotty/tools/dotc/core/Skolemization.scala | 122 ++++++++++++++++++ src/dotty/tools/dotc/core/TypeComparer.scala | 51 ++++---- src/dotty/tools/dotc/core/TypeOps.scala | 102 --------------- src/dotty/tools/dotc/core/Types.scala | 6 - 5 files changed, 150 insertions(+), 133 deletions(-) create mode 100644 src/dotty/tools/dotc/core/Skolemization.scala diff --git a/src/dotty/tools/dotc/core/NameOps.scala b/src/dotty/tools/dotc/core/NameOps.scala index 240447e6d43c..c15c4c4c2946 100644 --- a/src/dotty/tools/dotc/core/NameOps.scala +++ b/src/dotty/tools/dotc/core/NameOps.scala @@ -355,5 +355,5 @@ object NameOps { case NO_NAME => primitivePostfixMethodName case name => name } - } + } } \ No newline at end of file diff --git a/src/dotty/tools/dotc/core/Skolemization.scala b/src/dotty/tools/dotc/core/Skolemization.scala new file mode 100644 index 000000000000..592ec860b4e2 --- /dev/null +++ b/src/dotty/tools/dotc/core/Skolemization.scala @@ -0,0 +1,122 @@ +package dotty.tools.dotc +package core + +import Symbols._, Types._, Contexts._, Decorators._, NameOps._, StdNames._ +import collection.mutable + +trait Skolemization { + + protected var skolemsOutstanding = false + + def ensureSingleton(tp: Type)(implicit ctx: Context): SingletonType = tp.stripTypeVar match { + case tp: SingletonType => + tp + case tp: ValueType => + skolemsOutstanding = true + tp.narrow + case tp: TypeProxy => + ensureSingleton(tp.underlying) + } + + /** Approximate a type `tp` with a type that does not contain skolem termrefs. + * @param toSuper if true, return the smallest supertype of `tp` with this property + * e;se return the largest subtype. + */ + final def deSkolemize(tp: Type, toSuper: Boolean)(implicit ctx: Context): Type = + if (skolemsOutstanding) deSkolemize(tp, if (toSuper) 1 else -1, Set()) else tp + + private def deSkolemize(tp: Type, variance: Int, seen: Set[Symbol])(implicit ctx: Context): Type = + ctx.traceIndented(s"deskolemize $tp, variance = $variance, seen = $seen = ") { + def approx(lo: Type = defn.NothingType, hi: Type = defn.AnyType, newSeen: Set[Symbol] = seen) = + if (variance == 0) NoType + else deSkolemize(if (variance < 0) lo else hi, variance, newSeen) + tp match { + case tp: NamedType => + val sym = tp.symbol + if (sym.isSkolem) + if (seen contains sym) NoType + else approx(hi = sym.info, newSeen = seen + sym) + else if (sym.isStatic) tp + else { + val pre1 = deSkolemize(tp.prefix, variance, seen) + if (pre1.exists && !pre1.isRef(defn.NothingClass)) tp.derivedSelect(pre1) + else { + ctx.log(s"deskolem: $tp: ${tp.info}") + tp.info match { + case TypeBounds(lo, hi) => approx(lo, hi) + case info => approx(defn.NothingType, info) + } + } + } + case _: ThisType | _: BoundType | _: SuperType | NoType | NoPrefix => + tp + case tp: RefinedType => + val parent1 = deSkolemize(tp.parent, variance, seen) + if (parent1.exists) { + val refinedInfo1 = deSkolemize(tp.refinedInfo, variance, seen) + if (refinedInfo1.exists) + tp.derivedRefinedType(parent1, tp.refinedName, refinedInfo1) + else + approx(hi = parent1) + } + else approx() + case tp: TypeAlias => + val alias1 = deSkolemize(tp.alias, variance * tp.variance, seen) + if (alias1.exists) tp.derivedTypeAlias(alias1) + else approx(hi = TypeBounds.empty) + case tp: TypeBounds => + val lo1 = deSkolemize(tp.lo, -variance, seen) + val hi1 = deSkolemize(tp.hi, variance, seen) + if (lo1.exists && hi1.exists) tp.derivedTypeBounds(lo1, hi1) + else approx(hi = + if (lo1.exists) TypeBounds.lower(lo1) + else if (hi1.exists) TypeBounds.upper(hi1) + else TypeBounds.empty) + case tp: ClassInfo => + val pre1 = deSkolemize(tp.prefix, variance, seen) + if (pre1.exists) tp.derivedClassInfo(pre1) + else NoType + case tp: AndOrType => + val tp1d = deSkolemize(tp.tp1, variance, seen) + val tp2d = deSkolemize(tp.tp2, variance, seen) + if (tp1d.exists && tp2d.exists) + tp.derivedAndOrType(tp1d, tp2d) + else if (tp.isAnd) + approx(hi = tp1d & tp2d) // if one of tp1d, tp2d exists, it is the result of tp1d & tp2d + else + approx(lo = tp1d & tp2d) + case tp: WildcardType => + val bounds1 = deSkolemize(tp.optBounds, variance, seen) + if (bounds1.exists) tp.derivedWildcardType(bounds1) + else WildcardType + case _ => + if (tp.isInstanceOf[MethodicType]) assert(variance != 0, tp) + deSkolemizeMap.mapOver(tp, variance, seen) + } + } + + private var deSkolemizeMapCache: DeSkolemizeMap = null + + private def deSkolemizeMap(implicit ctx: Context) = { + if (deSkolemizeMapCache == null || deSkolemizeMapCache.definingCtx != ctx) + deSkolemizeMapCache = new DeSkolemizeMap + deSkolemizeMapCache + } + + private class DeSkolemizeMap(implicit ctx: Context) extends TypeMap { + def definingCtx = ctx + private var seen: Set[Symbol] = _ + def apply(tp: Type) = deSkolemize(tp, variance, seen) + def mapOver(tp: Type, variance: Int, seen: Set[Symbol]) = { + val savedVariance = this.variance + val savedSeen = this.seen + this.variance = variance + this.seen = seen + try super.mapOver(tp) + finally { + this.variance = savedVariance + this.seen = savedSeen + } + } + } +} \ No newline at end of file diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index 98a0d665c2d1..8ce1ad354e32 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -16,7 +16,7 @@ import scala.util.control.NonFatal /** Provides methods to compare types. */ -class TypeComparer(initctx: Context) extends DotClass { +class TypeComparer(initctx: Context) extends DotClass with Skolemization { implicit val ctx: Context = initctx val state = ctx.typerState @@ -267,8 +267,7 @@ class TypeComparer(initctx: Context) extends DotClass { } } - val bound = (if (false) ctx.deSkolemize(bound0, toSuper = fromBelow) else bound0) - .dealias.stripTypeVar + val bound = deSkolemize(bound0, toSuper = fromBelow).dealias.stripTypeVar def description = s"${param.show} ${if (fromBelow) ">:>" else "<:<"} ${bound.show} to ${constraint.show}" constr.println(s"adding $description") val res = addBi(param, bound, fromBelow) @@ -794,24 +793,28 @@ class TypeComparer(initctx: Context) extends DotClass { ctdSubType(orig1, orig2) } - def hasMatchingMember(name: Name, orig1: Type, tp2: RefinedType): Boolean = /*>|>*/ ctx.traceIndented(s"hasMatchingMember($orig1 . $name, ${tp2.refinedInfo}) ${orig1.member(name).info.show}", subtyping) /*<|<*/ { - val base = orig1.ensureSingleton - var rinfo2 = tp2.refinedInfo.substRefinedThis(0, base) - def qualifies(m: SingleDenotation) = isSubType(m.info, rinfo2) - def memberMatches(mbr: Denotation): Boolean = mbr match { // inlined hasAltWith for performance - case mbr: SingleDenotation => qualifies(mbr) - case _ => mbr hasAltWith qualifies - } - memberMatches(base member name) || - orig1.isInstanceOf[SingletonType] && - { // special case for situations like: - // foo <: C { type T = foo.T } - rinfo2 match { - case rinfo2: TypeAlias => - !ctx.phase.erasedTypes && (base select name) =:= rinfo2.alias - case _ => false - } + def hasMatchingMember(name: Name, tp1: Type, tp2: RefinedType): Boolean = /*>|>*/ ctx.traceIndented(s"hasMatchingMember($tp1 . $name, ${tp2.refinedInfo}) ${tp1.member(name).info.show}", subtyping) /*<|<*/ { + val saved = skolemsOutstanding + try { + val base = ensureSingleton(tp1) + var rinfo2 = tp2.refinedInfo.substRefinedThis(0, base) + def qualifies(m: SingleDenotation) = isSubType(m.info, rinfo2) + def memberMatches(mbr: Denotation): Boolean = mbr match { // inlined hasAltWith for performance + case mbr: SingleDenotation => qualifies(mbr) + case _ => mbr hasAltWith qualifies } + memberMatches(base member name) || + tp1.isInstanceOf[SingletonType] && + { // special case for situations like: + // foo <: C { type T = foo.T } + rinfo2 match { + case rinfo2: TypeAlias => + !ctx.phase.erasedTypes && (base select name) =:= rinfo2.alias + case _ => false + } + } + } + finally skolemsOutstanding = saved } /** A type has been covered previously in subtype checking if it @@ -864,7 +867,7 @@ class TypeComparer(initctx: Context) extends DotClass { def narrowGADTBounds(tr: NamedType, bound: Type, fromBelow: Boolean): Boolean = ctx.mode.is(Mode.GADTflexible) && { val tparam = tr.symbol - val bound1 = bound // ctx.deSkolemize(bound, toSuper = fromBelow) + val bound1 = deSkolemize(bound, toSuper = fromBelow) println(s"narrow gadt bound of $tparam: ${tparam.info} from ${if (fromBelow) "below" else "above"} to $bound1 ${bound1.isRef(tparam)}") !bound1.isRef(tparam) && { val oldBounds = ctx.gadt.bounds(tparam) @@ -1419,9 +1422,9 @@ class ExplainingTypeComparer(initctx: Context) extends TypeComparer(initctx) { super.isSubType(tp1, tp2) } - override def hasMatchingMember(name: Name, orig1: Type, tp2: RefinedType): Boolean = - traceIndented(s"hasMatchingMember(${show(orig1)} . $name, ${show(tp2.refinedInfo)}), member = ${show(orig1.member(name).info)}") { - super.hasMatchingMember(name, orig1, 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) = diff --git a/src/dotty/tools/dotc/core/TypeOps.scala b/src/dotty/tools/dotc/core/TypeOps.scala index fb6ef6c0917b..6fc7314c204a 100644 --- a/src/dotty/tools/dotc/core/TypeOps.scala +++ b/src/dotty/tools/dotc/core/TypeOps.scala @@ -87,108 +87,6 @@ trait TypeOps { this: Context => def apply(tp: Type) = simplify(tp, this) } - /** Approximate a type `tp` with a type that does not contain skolem termrefs. - * @param toSuper if true, return the smallest supertype of `tp` with this property - * e;se return the largest subtype. - */ - final def deSkolemize(tp: Type, toSuper: Boolean): Type = - deSkolemize(tp, if (toSuper) 1 else -1, Set()) - - private def deSkolemize(tp: Type, variance: Int, seen: Set[Symbol]): Type = - traceIndented(s"deskolemize $tp, variance = $variance, seen = $seen = ") { - def approx(lo: Type = defn.NothingType, hi: Type = defn.AnyType, newSeen: Set[Symbol] = seen) = - if (variance == 0) NoType - else deSkolemize(if (variance < 0) lo else hi, variance, newSeen) - tp match { - case tp: NamedType => - val sym = tp.symbol - if (sym.isSkolem) - if (seen contains sym) NoType - else approx(hi = sym.info, newSeen = seen + sym) - else if (sym.isStatic) tp - else { - val pre1 = deSkolemize(tp.prefix, variance, seen) - if (pre1.exists && !pre1.isRef(defn.NothingClass)) tp.derivedSelect(pre1) - else { - ctx.log(s"deskolem: $tp: ${tp.info}") - tp.info match { - case TypeBounds(lo, hi) => approx(lo, hi) - case info => approx(defn.NothingType, info) - } - } - } - case _: ThisType | _: BoundType | _: SuperType | NoType | NoPrefix => - tp - case tp: RefinedType => - val parent1 = deSkolemize(tp.parent, variance, seen) - if (parent1.exists) { - val refinedInfo1 = deSkolemize(tp.refinedInfo, variance, seen) - if (refinedInfo1.exists) - tp.derivedRefinedType(parent1, tp.refinedName, refinedInfo1) - else - approx(hi = parent1) - } - else approx() - case tp: TypeAlias => - val alias1 = deSkolemize(tp.alias, variance * tp.variance, seen) - if (alias1.exists) tp.derivedTypeAlias(alias1) - else approx(hi = TypeBounds.empty) - case tp: TypeBounds => - val lo1 = deSkolemize(tp.lo, -variance, seen) - val hi1 = deSkolemize(tp.hi, variance, seen) - if (lo1.exists && hi1.exists) tp.derivedTypeBounds(lo1, hi1) - else approx(hi = - if (lo1.exists) TypeBounds.lower(lo1) - else if (hi1.exists) TypeBounds.upper(hi1) - else TypeBounds.empty) - case tp: ClassInfo => - val pre1 = deSkolemize(tp.prefix, variance, seen) - if (pre1.exists) tp.derivedClassInfo(pre1) - else NoType - case tp: AndOrType => - val tp1d = deSkolemize(tp.tp1, variance, seen) - val tp2d = deSkolemize(tp.tp2, variance, seen) - if (tp1d.exists && tp2d.exists) - tp.derivedAndOrType(tp1d, tp2d) - else if (tp.isAnd) - approx(hi = tp1d & tp2d) // if one of tp1d, tp2d exists, it is the result of tp1d & tp2d - else - approx(lo = tp1d & tp2d) - case tp: WildcardType => - val bounds1 = deSkolemize(tp.optBounds, variance, seen) - if (bounds1.exists) tp.derivedWildcardType(bounds1) - else WildcardType - case _ => - if (tp.isInstanceOf[MethodicType]) assert(variance != 0, tp) - deSkolemizeMap.mapOver(tp, variance, seen) - } - } - - private var deSkolemizeMapCache: DeSkolemizeMap = null - - private def deSkolemizeMap = { - if (deSkolemizeMapCache == null || deSkolemizeMapCache.definingCtx != this) - deSkolemizeMapCache = new DeSkolemizeMap - deSkolemizeMapCache - } - - private class DeSkolemizeMap(implicit ctx: Context) extends TypeMap { - def definingCtx = ctx - private var seen: Set[Symbol] = _ - def apply(tp: Type) = deSkolemize(tp, variance, seen) - def mapOver(tp: Type, variance: Int, seen: Set[Symbol]) = { - val savedVariance = this.variance - val savedSeen = this.seen - this.variance = variance - this.seen = seen - try super.mapOver(tp) - finally { - this.variance = savedVariance - this.seen = savedSeen - } - } - } - /** Approximate union type by intersection of its dominators. * See Type#approximateUnion for an explanation. */ diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index c6913eb0f2cf..920c9c6f8c76 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -755,12 +755,6 @@ object Types { def narrow(implicit ctx: Context): TermRef = TermRef(NoPrefix, ctx.newSkolem(this)) - def ensureSingleton(implicit ctx: Context): SingletonType = stripTypeVar match { - case tp: SingletonType => tp - case tp: ValueType => narrow - case tp: TypeProxy => tp.underlying.ensureSingleton - } - // ----- Normalizing typerefs over refined types ---------------------------- /** If this is a refinement type that has a refinement for `name` (which might be followed From 90f2668220645df7b654827c2dfdd1100c878ac2 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 9 Jan 2015 09:52:00 +0100 Subject: [PATCH 20/72] Optimize for case where RefinedThis is absent Since we cache the information whether a refinement contains RefinedThis occurrences to the refinement itself, we can use this info to avoid substututing RefinedThis types. Used in findMember and hasMatchingMember. The commit uncovered an issue with constraint handling that will be fixed in the next commit. --- src/dotty/tools/dotc/core/TypeComparer.scala | 8 ++++++-- src/dotty/tools/dotc/core/Types.scala | 4 +++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index 8ce1ad354e32..79ec7ad3c8cc 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -796,8 +796,12 @@ class TypeComparer(initctx: Context) extends DotClass with Skolemization { def hasMatchingMember(name: Name, tp1: Type, tp2: RefinedType): Boolean = /*>|>*/ ctx.traceIndented(s"hasMatchingMember($tp1 . $name, ${tp2.refinedInfo}) ${tp1.member(name).info.show}", subtyping) /*<|<*/ { val saved = skolemsOutstanding try { - val base = ensureSingleton(tp1) - var rinfo2 = tp2.refinedInfo.substRefinedThis(0, base) + var base = tp1 + var rinfo2 = tp2.refinedInfo + if (tp2.refinementRefersToThis) { + base = ensureSingleton(base) + rinfo2 = rinfo2.substRefinedThis(0, base) + } def qualifies(m: SingleDenotation) = isSubType(m.info, rinfo2) def memberMatches(mbr: Denotation): Boolean = mbr match { // inlined hasAltWith for performance case mbr: SingleDenotation => qualifies(mbr) diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 920c9c6f8c76..21997c5fa688 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -438,7 +438,9 @@ object Types { val pdenot = go(tp.parent) val rinfo = pre match { case pre: RefinedType => tp.refinedInfo.substThis0(tp, RefinedThis(pre, -1)) - case _ => tp.refinedInfo.substRefinedThis(0, pre) + case _ => + if (tp.refinementRefersToThis) tp.refinedInfo.substRefinedThis(0, pre) + else tp.refinedInfo } if (Types.goRefinedCheck) { val rinfo0 = tp.refinedInfo.substThis0(tp, pre) From cb103dbc6fbca9d2ea19030e83dad3690cf394a9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 9 Jan 2015 09:57:01 +0100 Subject: [PATCH 21/72] Fix problem in constraint handling After last commit, dotc/config died with the "isSatisfiable" assertion in TypeComparer. The problem was that when deeling with a variable/variable constraint A <: B we treated this as the two independent actions of adding A <: B B >: A But this means we no longer have a clean inductive satisfiability check before something gets added - A <: B gets added before the satisfiability check of B >: A is started. The fix splits satisfiability check and actual constraint update in two separate actions. --- src/dotty/tools/dotc/config/Config.scala | 5 + src/dotty/tools/dotc/core/TypeComparer.scala | 180 ++++++++++--------- 2 files changed, 105 insertions(+), 80 deletions(-) diff --git a/src/dotty/tools/dotc/config/Config.scala b/src/dotty/tools/dotc/config/Config.scala index e1657a273ce5..61f8bc99ea76 100644 --- a/src/dotty/tools/dotc/config/Config.scala +++ b/src/dotty/tools/dotc/config/Config.scala @@ -21,6 +21,11 @@ object Config { */ final val checkConstraintsNonCyclicTrans = false + /** Check that each constraint resulting from a subtype test + * is satisfiable. + */ + final val checkConstraintsSatisfiable = false + /** Type comparer will fail with an assert if the upper bound * of a constrained parameter becomes Nothing. This should be turned * on only for specific debugging as normally instantiation to Nothing diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index 79ec7ad3c8cc..3e9d553a1e66 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -34,6 +34,18 @@ class TypeComparer(initctx: Context) extends DotClass with Skolemization { */ protected var ignoreConstraint = false + def ignoringConstraint[T](op: => T): T = { + val savedIgnore = ignoreConstraint + val savedFrozen = frozenConstraint + ignoreConstraint = true + frozenConstraint = true + try op + finally { + ignoreConstraint = savedIgnore + frozenConstraint = savedFrozen + } + } + /** Compare a solution of the constraint instead of the constrained parameters. * The solution maps every parameter to its lower bound. */ @@ -41,14 +53,9 @@ class TypeComparer(initctx: Context) extends DotClass with Skolemization { private var needsGc = false - /** Bounds for constrained parameters yet to be added to the constraint */ - private var pendingBoundss: SimpleMap[PolyParam, TypeBounds] = SimpleMap.Empty - - /** If `param` is in `needsSatCheck` then the constraint should be checked - * for satisfiability after `param`'s bound are updated. - */ - private var needsSatCheck: Set[PolyParam] = Set() - + /** The parameters currently being constrained by addConstraint */ + private var pendingParams: Set[PolyParam] = Set() + /** Is a subtype check in course? In that case we may not * permanently instantiate type variables, because the corresponding * constraint might still be retracted and the instantiation should @@ -173,107 +180,117 @@ class TypeComparer(initctx: Context) extends DotClass with Skolemization { * make sure 'B >: A' gets added and vice versa. Furthermore, if the constraint * implies 'A <: B <: A', A and B get unified. */ - def addBi(param: PolyParam, bound: Type, fromBelow: Boolean): Boolean = + def addc(param: PolyParam, bound: Type, fromBelow: Boolean): Boolean = constraint.bounds(param) match { case TypeBounds(plo: PolyParam, phi) if (plo eq phi) && constraint.contains(plo) => - addBi(plo, bound, fromBelow) - case _ => + addc(plo, bound, fromBelow) + case pbounds0 => bound match { case bound: PolyParam if constraint contains bound => - val TypeBounds(lo, hi) = constraint.bounds(bound) + val bbounds0 @ TypeBounds(lo, hi) = constraint.bounds(bound) if (lo eq hi) - addBi(param, lo, fromBelow) + addc(param, lo, fromBelow) else if (param == bound) true else if (fromBelow && param.occursIn(lo, fromBelow = true)) unify(param, bound) else if (!fromBelow && param.occursIn(hi, fromBelow = false)) unify(bound, param) - else - addUni(param, bound, fromBelow) && - addUni(bound, param, !fromBelow) + else { + val pbounds = prepare(param, bound, fromBelow) + val bbounds = prepare(bound, param, !fromBelow) + pbounds.exists && bbounds.exists && { + install(param, pbounds.bounds, pbounds0) + install(bound, bbounds.bounds, bbounds0) + true + } + } case bound: AndOrType if fromBelow != bound.isAnd => - addBi(param, bound.tp1, fromBelow) && - addBi(param, bound.tp2, fromBelow) + addc(param, bound.tp1, fromBelow) && + addc(param, bound.tp2, fromBelow) case bound: WildcardType => true case bound => // !!! remove to keep the originals - addUni(param, bound, fromBelow) + val pbounds = prepare(param, bound, fromBelow) + pbounds.exists && { + install(param, pbounds.bounds, pbounds0) + true + } } } - /** Add constraint without propagating in the other direction or unifying */ - def addUni(param: PolyParam, bound: Type, fromBelow: Boolean): Boolean = { + /** Install bounds for param */ + def install(param: PolyParam, newBounds: TypeBounds, oldBounds: TypeBounds): Unit = { + val curBounds = constraint.bounds(param) + constraint = constraint.updated(param, newBounds) + if (curBounds ne oldBounds) { + // In this case the bounds were updated previously by a recursive isSubType in + // the satisfiability check of prepare. Reapply the previously added bounds, but + // go through a full addConstraint in order to eliminate any cyclic dependencies + // via unification. + if (!ignoringConstraint(isSubType(curBounds.lo, newBounds.lo))) + addConstraint(param, curBounds.lo, fromBelow) + if (!ignoringConstraint(isSubType(newBounds.hi, curBounds.hi))) + addConstraint(param, curBounds.hi, !fromBelow) + } + } + + /** Compute new bounds for `param` and check whether they are + * satisfiable. The check might in turn trigger other additions to the constraint. + * @return The new bounds for `param` (which are not installed yet), or + * NoType, if the new constraint would not be satisfiable. + */ + def prepare(param: PolyParam, bound: Type, fromBelow: Boolean): Type = { + constr.println(s"prepare ${param.show} ${if (fromBelow) ">:>" else "<:<"} ${bound.show}") val oldBounds = constraint.bounds(param) - def narrowedBounds(bounds: TypeBounds): TypeBounds = { - val savedIgnore = ignoreConstraint - val savedFrozen = frozenConstraint - ignoreConstraint = true - frozenConstraint = true - try - if (fromBelow) bounds.derivedTypeBounds(bounds.lo | bound, bounds.hi) - else bounds.derivedTypeBounds(bounds.lo, bounds.hi & bound) - finally { - ignoreConstraint = savedIgnore - frozenConstraint = savedFrozen - } + val newBounds = ignoringConstraint { + if (fromBelow) oldBounds.derivedTypeBounds(oldBounds.lo | bound, oldBounds.hi) + else oldBounds.derivedTypeBounds(oldBounds.lo, oldBounds.hi & bound) } - val newBounds = narrowedBounds(oldBounds) - (param == bound) || - (oldBounds eq newBounds) || - { val pendingBounds = pendingBoundss(param) - if (pendingBounds == null) - // Why the pendingBoundss tests? It is possible that recursive subtype invocations - // come back with another constraint for `param`. An example came up when compiling - // ElimRepeated where we got the constraint - // - // Coll <: IterableLike[Tree, Coll] - // - // and added - // - // List[Tree] <: Coll - // - // The recursive bounds test is then - // - // List[Tree] <: IterableLike[Tree, Coll] - // - // and because of the F-bounded polymorphism in the supertype of List, - // i.e. List[T] <: IterableLike[T, List[T]], this leads again to - // - // List[Tree] <: Coll - try { - pendingBoundss = pendingBoundss.updated(param, newBounds) - isSubType(newBounds.lo, newBounds.hi) && { - constraint = constraint.updated(param, pendingBoundss(param)) - if (needsSatCheck(param)) { - assert(isSatisfiable) - needsSatCheck -= param - } + val ok = + (param == bound) || + (oldBounds eq newBounds) || + { + if (pendingParams contains param) { + // Why the pendingParams test? It is possible that recursive subtype invocations + // come back with another constraint for `param`. An example came up when compiling + // ElimRepeated where we got the constraint + // + // Coll <: IterableLike[Tree, Coll] + // + // and added + // + // List[Tree] <: Coll + // + // The recursive bounds test is then + // + // List[Tree] <: IterableLike[Tree, Coll] + // + // and because of the F-bounded polymorphism in the supertype of List, + // i.e. List[T] <: IterableLike[T, List[T]], this leads again to + // + // List[Tree] <: Coll + // + // If a parameter is already pending, we avoid revisiting it here. + // Instead we combine the bounds computed here with the originally + // computed bounds when installing the original type. + constr.println(i"deferred bounds: $param $newBounds") true + } else { + pendingParams += param + try isSubType(newBounds.lo, newBounds.hi) + finally pendingParams -= param } } - finally pendingBoundss = pendingBoundss.remove(param) - else { - // TODO: investigate - if the last line in this comment is uncommented, we get a cyclic - // constraint error in tools/io. For now, we do without even though this - // risks allowing unsatisfiable constraints to get through. - // Unsatisfiable constraints are caught by an assertion that is executed later - // in case we got here. - //pendingBoundss = pendingBoundss.updated(param, narrowedBounds(pendingBounds)) - - needsSatCheck += param - true - } - } + if (ok) newBounds else NoType } val bound = deSkolemize(bound0, toSuper = fromBelow).dealias.stripTypeVar def description = s"${param.show} ${if (fromBelow) ">:>" else "<:<"} ${bound.show} to ${constraint.show}" constr.println(s"adding $description") - val res = addBi(param, bound, fromBelow) + val res = addc(param, bound, fromBelow) constr.println(s"added $description") if (Config.checkConstraintsNonCyclicTrans) constraint.checkNonCyclicTrans() - if (needsSatCheck(param)) assert(isSatisfiable) res } @@ -313,7 +330,10 @@ class TypeComparer(initctx: Context) extends DotClass with Skolemization { if ((tp2 eq tp1) || (tp2 eq WildcardType) || (tp2 eq AnyType) && tp1.isValueType) return true - isSubType(tp1, tp2) + try isSubType(tp1, tp2) + finally + if (Config.checkConstraintsSatisfiable) + assert(isSatisfiable, constraint.show) } protected def isSubTypeWhenFrozen(tp1: Type, tp2: Type): Boolean = { From 53db7c8e1090a72ead3d795b4715f04863e62f42 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 9 Jan 2015 13:28:44 +0100 Subject: [PATCH 22/72] New fast path for checking refined types. As before, optimize for the case where correspnding refinements have the same name and the lower one is a type alias. --- src/dotty/tools/dotc/core/TypeComparer.scala | 52 +++++++++++++++++--- 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index 3e9d553a1e66..aec90459b8e8 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -614,15 +614,24 @@ class TypeComparer(initctx: Context) extends DotClass with Skolemization { fourthTry(tp1, tp2) } compareNamed - case tp2 @ RefinedType(parent2, name2) => + case tp2: RefinedType => def compareRefined: Boolean = { - val normalPath = - ctdSubType(tp1, parent2) && ( - name2 == nme.WILDCARD - || hasMatchingMember(name2, tp1, tp2) - || fourthTry(tp1, tp2)) - normalPath || - needsEtaLift(tp1, tp2) && tp1.testLifted(tp2.typeParams, isSubType(_, tp2)) + val tp1w = tp1.widen + val skipped2 = skipMatching(tp1w, tp2) + if (skipped2 eq tp2) { + val name2 = tp2.refinedName + val normalPath = + ctdSubType(tp1, tp2.parent) && + ( name2 == nme.WILDCARD + || hasMatchingMember(name2, tp1, tp2) + || fourthTry(tp1, tp2) + ) + normalPath || + needsEtaLift(tp1, tp2) && tp1.testLifted(tp2.typeParams, isSubType(_, tp2)) + } + else // fast path, in particular for refinements resulting from parameterization. + ctdSubType(tp1, skipped2) && + isSubRefinements(tp1w.asInstanceOf[RefinedType], tp2, skipped2) } compareRefined case OrType(tp21, tp22) => @@ -841,6 +850,33 @@ class TypeComparer(initctx: Context) extends DotClass with Skolemization { finally skolemsOutstanding = saved } + /** Skip refinements in `tp2` which match corresponding refinements in `tp1`. + * "Match" means: They appear in the same order, refine the same names, and + * the refinement in `tp1` is an alias type. + * @return The parent type of `tp2` after skipping the matching refinements. + */ + def skipMatching(tp1w: Type, tp2: RefinedType): Type = tp1w match { + case tp1w @ RefinedType(parent1, name1) + if name1 == tp2.refinedName && tp1w.refinedInfo.isInstanceOf[TypeAlias] => + 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`. + * The precondition is established by `skipMatching`. + */ + def isSubRefinements(tp1: RefinedType, tp2: RefinedType, limit: Type): Boolean = + isSubType(tp1.refinedInfo, 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, AndTypes or AnnotatedTypes. From f11a0a533dd628684f9d255df97c2af54664b103 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 9 Jan 2015 13:43:43 +0100 Subject: [PATCH 23/72] Remove unnecessary nested methods in TypeComparer. The previous idea to keep original types around in `origN` types to be accessed from nested methods was not needed in the end. --- src/dotty/tools/dotc/core/TypeComparer.scala | 975 +++++++++---------- 1 file changed, 478 insertions(+), 497 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index aec90459b8e8..2b0391538811 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -32,31 +32,31 @@ class TypeComparer(initctx: Context) extends DotClass with Skolemization { * declared bounds of PolyParams. Used when forming unions and intersectons * of constraint bounds */ - protected var ignoreConstraint = false - - def ignoringConstraint[T](op: => T): T = { - val savedIgnore = ignoreConstraint - val savedFrozen = frozenConstraint - ignoreConstraint = true - frozenConstraint = true - try op - finally { - ignoreConstraint = savedIgnore - frozenConstraint = savedFrozen - } + private var ignoreConstraint = false + + private def ignoringConstraint[T](op: => T): T = { + val savedIgnore = ignoreConstraint + val savedFrozen = frozenConstraint + ignoreConstraint = true + frozenConstraint = true + try op + finally { + ignoreConstraint = savedIgnore + frozenConstraint = savedFrozen } + } /** Compare a solution of the constraint instead of the constrained parameters. * The solution maps every parameter to its lower bound. */ protected var solvedConstraint = false - private var needsGc = false - /** The parameters currently being constrained by addConstraint */ private var pendingParams: Set[PolyParam] = Set() - - /** Is a subtype check in course? In that case we may not + + 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. @@ -106,20 +106,20 @@ class TypeComparer(initctx: Context) extends DotClass with Skolemization { * Constraints are required to be in normalized form. This means * (1) if P <: Q in C then also Q >: P in C * (2) if P r Q in C and Q r R in C then also P r R in C, where r is <: or :> - * - * "P <: Q in C" means here: There is a constraint P <: H[Q], + * + * "P <: Q in C" means here: There is a constraint P <: H[Q], * where H is the multi-hole context given by: - * + * * H = [] * H & T * T & H * H | H - * + * * (the idea is that a parameter Q in a H context is guaranteed to be a supertype of P). - * - * "P >: Q in C" means: There is a constraint P >: L[Q], + * + * "P >: Q in C" means: There is a constraint P >: L[Q], * where L is the multi-hole context given by: - * + * * L = [] * L | T * T | L @@ -163,7 +163,7 @@ class TypeComparer(initctx: Context) extends DotClass with Skolemization { val bounds = constraint1.bounds(p1) isSubType(bounds.lo, bounds.hi) && { constraint = constraint1; true } } - + /** If current constraint set is not frozen, add the constraint * * param >: bound if fromBelow is true @@ -175,14 +175,14 @@ class TypeComparer(initctx: Context) extends DotClass with Skolemization { * @return Whether the augmented constraint is still satisfiable. */ def addConstraint(param: PolyParam, bound0: Type, fromBelow: Boolean): Boolean = { - + /** Add bidirectional constraint. If new constraint implies 'A <: B' we also * make sure 'B >: A' gets added and vice versa. Furthermore, if the constraint - * implies 'A <: B <: A', A and B get unified. + * implies 'A <: B <: A', A and B get unified. */ - def addc(param: PolyParam, bound: Type, fromBelow: Boolean): Boolean = + def addc(param: PolyParam, bound: Type, fromBelow: Boolean): Boolean = constraint.bounds(param) match { - case TypeBounds(plo: PolyParam, phi) if (plo eq phi) && constraint.contains(plo) => + case TypeBounds(plo: PolyParam, phi) if (plo eq phi) && constraint.contains(plo) => addc(plo, bound, fromBelow) case pbounds0 => bound match { @@ -218,26 +218,26 @@ class TypeComparer(initctx: Context) extends DotClass with Skolemization { } } } - + /** Install bounds for param */ def install(param: PolyParam, newBounds: TypeBounds, oldBounds: TypeBounds): Unit = { val curBounds = constraint.bounds(param) constraint = constraint.updated(param, newBounds) if (curBounds ne oldBounds) { - // In this case the bounds were updated previously by a recursive isSubType in + // In this case the bounds were updated previously by a recursive isSubType in // the satisfiability check of prepare. Reapply the previously added bounds, but // go through a full addConstraint in order to eliminate any cyclic dependencies - // via unification. + // via unification. if (!ignoringConstraint(isSubType(curBounds.lo, newBounds.lo))) addConstraint(param, curBounds.lo, fromBelow) if (!ignoringConstraint(isSubType(newBounds.hi, curBounds.hi))) addConstraint(param, curBounds.hi, !fromBelow) } } - + /** Compute new bounds for `param` and check whether they are * satisfiable. The check might in turn trigger other additions to the constraint. - * @return The new bounds for `param` (which are not installed yet), or + * @return The new bounds for `param` (which are not installed yet), or * NoType, if the new constraint would not be satisfiable. */ def prepare(param: PolyParam, bound: Type, fromBelow: Boolean): Type = { @@ -253,12 +253,12 @@ class TypeComparer(initctx: Context) extends DotClass with Skolemization { { if (pendingParams contains param) { // Why the pendingParams test? It is possible that recursive subtype invocations - // come back with another constraint for `param`. An example came up when compiling + // come back with another constraint for `param`. An example came up when compiling // ElimRepeated where we got the constraint // // Coll <: IterableLike[Tree, Coll] // - // and added + // and added // // List[Tree] <: Coll // @@ -266,12 +266,12 @@ class TypeComparer(initctx: Context) extends DotClass with Skolemization { // // List[Tree] <: IterableLike[Tree, Coll] // - // and because of the F-bounded polymorphism in the supertype of List, + // and because of the F-bounded polymorphism in the supertype of List, // i.e. List[T] <: IterableLike[T, List[T]], this leads again to // // List[Tree] <: Coll - // - // If a parameter is already pending, we avoid revisiting it here. + // + // If a parameter is already pending, we avoid revisiting it here. // Instead we combine the bounds computed here with the originally // computed bounds when installing the original type. constr.println(i"deferred bounds: $param $newBounds") @@ -284,7 +284,7 @@ class TypeComparer(initctx: Context) extends DotClass with Skolemization { } if (ok) newBounds else NoType } - + val bound = deSkolemize(bound0, toSuper = fromBelow).dealias.stripTypeVar def description = s"${param.show} ${if (fromBelow) ">:>" else "<:<"} ${bound.show} to ${constraint.show}" constr.println(s"adding $description") @@ -331,498 +331,479 @@ class TypeComparer(initctx: Context) extends DotClass with Skolemization { (tp2 eq WildcardType) || (tp2 eq AnyType) && tp1.isValueType) return true try isSubType(tp1, tp2) - finally - if (Config.checkConstraintsSatisfiable) + finally + if (Config.checkConstraintsSatisfiable) assert(isSatisfiable, constraint.show) } - protected def isSubTypeWhenFrozen(tp1: Type, tp2: Type): Boolean = { - val saved = frozenConstraint - frozenConstraint = true - try isSubType(tp1, tp2) - finally frozenConstraint = saved - } - private def traceInfo(tp1: Type, tp2: Type) = s"${tp1.show} <:< ${tp2.show}" + (if (ctx.settings.verbose.value) s" ${tp1.getClass} ${tp2.getClass}${if (frozenConstraint) " frozen" else ""}" else "") - def isSubType(orig1: Type, orig2: Type): Boolean = { - - def ctdSubType(tp1: Type, tp2: Type): Boolean = /*>|>*/ ctx.traceIndented(s"isSubType ${traceInfo(tp1, tp2)}, class1 = ${tp1.getClass}, class2 = ${tp2.getClass}", subtyping) /*<|<*/ { - if (tp2 eq NoType) false - else if (tp1 eq tp2) true - else { - val saved = constraint - val savedSuccessCount = successCount - val savedRLC = Types.reverseLevelCheck // !!! TODO: remove - Types.reverseLevelCheck = false - try { - recCount = recCount + 1 - val result = - if (recCount < LogPendingSubTypesThreshold) firstTry(tp1, tp2) - else monitoredIsSubType(tp1, tp2) - recCount = recCount - 1 - if (!result) constraint = saved - else if (recCount == 0 && needsGc) state.gc() - - def recordStatistics = { - // Stats.record(s"isSubType ${tp1.show} <:< ${tp2.show}") - totalCount += 1 - if (result) successCount += 1 else successCount = savedSuccessCount - if (recCount == 0) { - Stats.record("successful subType", successCount) - Stats.record("total subType", totalCount) - successCount = 0 - totalCount = 0 - } + def isSubType(tp1: Type, tp2: Type): Boolean = ctx.traceIndented(s"isSubType ${traceInfo(tp1, tp2)}, class1 = ${tp1.getClass}, class2 = ${tp2.getClass}", subtyping) /*<|<*/ { + if (tp2 eq NoType) false + else if (tp1 eq tp2) true + else { + val saved = constraint + val savedSuccessCount = successCount + val savedRLC = Types.reverseLevelCheck // !!! TODO: remove + Types.reverseLevelCheck = false + try { + recCount = recCount + 1 + val result = + if (recCount < LogPendingSubTypesThreshold) firstTry(tp1, tp2) + else monitoredIsSubType(tp1, tp2) + recCount = recCount - 1 + if (!result) constraint = saved + else if (recCount == 0 && needsGc) state.gc() + + def recordStatistics = { + // Stats.record(s"isSubType ${tp1.show} <:< ${tp2.show}") + totalCount += 1 + if (result) successCount += 1 else successCount = savedSuccessCount + if (recCount == 0) { + Stats.record("successful subType", successCount) + Stats.record("total subType", totalCount) + successCount = 0 + totalCount = 0 } - if (Stats.monitored) recordStatistics - - result - } catch { - case NonFatal(ex) => - def showState = { - println(disambiguated(implicit ctx => s"assertion failure for ${tp1.show} <:< ${tp2.show}, frozen = $frozenConstraint")) - def explainPoly(tp: Type) = tp match { - case tp: PolyParam => println(s"polyparam ${tp.show} found in ${tp.binder.show}") - case tp: TypeRef if tp.symbol.exists => println(s"typeref ${tp.show} found in ${tp.symbol.owner.show}") - case tp: TypeVar => println(s"typevar ${tp.show}, origin = ${tp.origin}") - case _ => println(s"${tp.show} is a ${tp.getClass}") - } - explainPoly(tp1) - explainPoly(tp2) - } - if (ex.isInstanceOf[AssertionError]) showState - recCount -= 1 - constraint = saved - successCount = savedSuccessCount - throw ex - } finally { - Types.reverseLevelCheck = savedRLC } + if (Stats.monitored) recordStatistics + + result + } catch { + case NonFatal(ex) => + def showState = { + println(disambiguated(implicit ctx => s"assertion failure for ${tp1.show} <:< ${tp2.show}, frozen = $frozenConstraint")) + def explainPoly(tp: Type) = tp match { + case tp: PolyParam => println(s"polyparam ${tp.show} found in ${tp.binder.show}") + case tp: TypeRef if tp.symbol.exists => println(s"typeref ${tp.show} found in ${tp.symbol.owner.show}") + case tp: TypeVar => println(s"typevar ${tp.show}, origin = ${tp.origin}") + case _ => println(s"${tp.show} is a ${tp.getClass}") + } + explainPoly(tp1) + explainPoly(tp2) + } + if (ex.isInstanceOf[AssertionError]) showState + recCount -= 1 + constraint = saved + successCount = savedSuccessCount + throw ex + } finally { + Types.reverseLevelCheck = savedRLC } } - - def narrowRefined(tp: Type): Type = tp match { - case tp: RefinedType => RefinedThis(tp, 0) // !!! TODO check that we can drop narrowRefined entirely - case _ => tp + } + + 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}") + 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 + } } + } - def firstTry(tp1: Type, tp2: Type): Boolean = { - tp2 match { - case tp2: NamedType => - def isHKSubType = tp2.name == tpnme.Apply && { - val lambda2 = tp2.prefix.LambdaClass(forcing = true) - lambda2.exists && !tp1.isLambda && - tp1.testLifted(lambda2.typeParams, isSubType(_, tp2.prefix)) - } - def compareNamed = { - implicit val ctx: Context = this.ctx // Dotty deviation: implicits need explicit type - tp1 match { - case tp1: NamedType => - val sym1 = tp1.symbol - (if ((sym1 ne NoSymbol) && (sym1 eq tp2.symbol)) ( - ctx.erasedTypes - || sym1.isStaticOwner - || { // Implements: A # X <: B # X - // if either A =:= B (i.e. A <: B and B <: A), or the following three conditions hold: - // 1. X is a class type, - // 2. B is a class type without abstract type members. - // 3. A <: B. - // Dealiasing is taken care of elsewhere. - val pre1 = tp1.prefix - val pre2 = tp2.prefix - ( isSameType(pre1, pre2) - || sym1.isClass - && pre2.classSymbol.exists - && pre2.abstractTypeMembers.isEmpty - && isSubType(pre1, pre2) - ) - } - ) - else (tp1.name eq tp2.name) && isSameType(tp1.prefix, tp2.prefix) - ) || isHKSubType || secondTryNamed(tp1, tp2) - case tp1: ThisType if tp1.cls eq tp2.symbol.moduleClass => - isSubType(tp1.cls.owner.thisType, tp2.prefix) - case _ => - isHKSubType || secondTry(tp1, tp2) - } - } - compareNamed - case tp2: ProtoType => - isMatchedByProto(tp2, tp1) - case tp2: PolyParam => - def comparePolyParam = - tp2 == tp1 || { - if (solvedConstraint && (constraint contains tp2)) ctdSubType(tp1, bounds(tp2).lo) - else - ctdSubTypeWhenFrozen(tp1, bounds(tp2).lo) || { - if (isConstrained(tp2)) addConstraint(tp2, tp1.widenExpr, fromBelow = true) - else (ctx.mode is Mode.TypevarsMissContext) || secondTry(tp1, tp2) - } - } - comparePolyParam - case tp2: BoundType => - tp2 == tp1 || secondTry(tp1, tp2) - case tp2: TypeVar => - ctdSubType(tp1, tp2.underlying) - case tp2: WildcardType => - def compareWild = tp2.optBounds match { - case TypeBounds(_, hi) => ctdSubType(tp1, hi) - case NoType => true - } - compareWild - case tp2: LazyRef => - ctdSubType(tp1, tp2.ref) - case tp2: AnnotatedType => - ctdSubType(tp1, tp2.tpe) // todo: refine? - case tp2: ThisType => - 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. - tp1.cls.classInfo.selfType.derivesFrom(tp2.cls) && - tp2.cls.classInfo.selfType.derivesFrom(tp1.cls) - case _ => - secondTry(tp1, tp2) - } - case tp2: SuperType => + private def firstTry(tp1: Type, tp2: Type): Boolean = { + tp2 match { + case tp2: NamedType => + def isHKSubType = tp2.name == tpnme.Apply && { + val lambda2 = tp2.prefix.LambdaClass(forcing = true) + lambda2.exists && !tp1.isLambda && + tp1.testLifted(lambda2.typeParams, isSubType(_, tp2.prefix)) + } + def compareNamed = { + implicit val ctx: Context = this.ctx // Dotty deviation: implicits need explicit type tp1 match { - case tp1: SuperType => - ctdSubType(tp1.thistpe, tp2.thistpe) && - isSameType(tp1.supertpe, tp2.supertpe) + case tp1: NamedType => + val sym1 = tp1.symbol + (if ((sym1 ne NoSymbol) && (sym1 eq tp2.symbol)) ( + ctx.erasedTypes + || sym1.isStaticOwner + || { // Implements: A # X <: B # X + // if either A =:= B (i.e. A <: B and B <: A), or the following three conditions hold: + // 1. X is a class type, + // 2. B is a class type without abstract type members. + // 3. A <: B. + // Dealiasing is taken care of elsewhere. + val pre1 = tp1.prefix + val pre2 = tp2.prefix + ( isSameType(pre1, pre2) + || sym1.isClass + && pre2.classSymbol.exists + && pre2.abstractTypeMembers.isEmpty + && isSubType(pre1, pre2) + ) + } + ) + else (tp1.name eq tp2.name) && isSameType(tp1.prefix, tp2.prefix) + ) || isHKSubType || secondTryNamed(tp1, tp2) + case tp1: ThisType if tp1.cls eq tp2.symbol.moduleClass => + isSubType(tp1.cls.owner.thisType, tp2.prefix) case _ => - secondTry(tp1, tp2) + isHKSubType || secondTry(tp1, tp2) } - case AndType(tp21, tp22) => - ctdSubType(tp1, tp21) && ctdSubType(tp1, tp22) - case ErrorType => - true - case _ => - secondTry(tp1, tp2) - } - } - - def secondTry(tp1: Type, tp2: Type): Boolean = tp1 match { - case tp1: NamedType => - tp2 match { - case tp2: ThisType if tp2.cls eq tp1.symbol.moduleClass => - isSubType(tp1.prefix, tp2.cls.owner.thisType) - case _ => - secondTryNamed(tp1, tp2) } - case OrType(tp11, tp12) => - isSubType(tp11, tp2) && isSubType(tp12, tp2) - case tp1: PolyParam => + compareNamed + case tp2: ProtoType => + isMatchedByProto(tp2, tp1) + case tp2: PolyParam => def comparePolyParam = - tp1 == tp2 || { - if (solvedConstraint && (constraint contains tp1)) isSubType(bounds(tp1).lo, tp2) + tp2 == tp1 || { + if (solvedConstraint && (constraint contains tp2)) isSubType(tp1, bounds(tp2).lo) else - isSubTypeWhenFrozen(bounds(tp1).hi, tp2) || { - if (isConstrained(tp1)) - addConstraint(tp1, tp2, fromBelow = false) && { - if ((!frozenConstraint) && - (tp2 isRef defn.NothingClass) && - state.isGlobalCommittable) { - def msg = s"!!! instantiated to Nothing: $tp1, constraint = ${constraint.show}" - if (Config.flagInstantiationToNothing) assert(false, msg) - else ctx.log(msg) - } - true - } - else (ctx.mode is Mode.TypevarsMissContext) || thirdTry(tp1, tp2) + isSubTypeWhenFrozen(tp1, bounds(tp2).lo) || { + if (isConstrained(tp2)) addConstraint(tp2, tp1.widenExpr, fromBelow = true) + else (ctx.mode is Mode.TypevarsMissContext) || secondTry(tp1, tp2) } } comparePolyParam - case tp1: RefinedThis => - tp2 match { - case tp2: RefinedThis if tp1.level == tp2.level => true - case _ => thirdTry(tp1, tp2) - } - case tp1: BoundType => - tp1 == tp2 || thirdTry(tp1, tp2) - case tp1: TypeVar => - (tp1 eq tp2) || isSubType(tp1.underlying, tp2) - case tp1: WildcardType => - def compareWild = tp1.optBounds match { - case TypeBounds(lo, _) => isSubType(lo, tp2) - case _ => true + 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 tp1: LazyRef => - isSubType(tp1.ref, tp2) - case tp1: AnnotatedType => - isSubType(tp1.tpe, tp2) + case tp2: LazyRef => + isSubType(tp1, tp2.ref) + case tp2: AnnotatedType => + isSubType(tp1, tp2.tpe) // todo: refine? + case tp2: ThisType => + 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. + tp1.cls.classInfo.selfType.derivesFrom(tp2.cls) && + tp2.cls.classInfo.selfType.derivesFrom(tp1.cls) + case _ => + secondTry(tp1, tp2) + } + case tp2: SuperType => + tp1 match { + case tp1: SuperType => + isSubType(tp1.thistpe, tp2.thistpe) && + isSameType(tp1.supertpe, tp2.supertpe) + case _ => + secondTry(tp1, tp2) + } + case AndType(tp21, tp22) => + isSubType(tp1, tp21) && isSubType(tp1, tp22) case ErrorType => true case _ => - thirdTry(tp1, tp2) + secondTry(tp1, tp2) } + } - def secondTryNamed(tp1: NamedType, tp2: Type): Boolean = { - tp1.info match { - // There was the following code, which was meant to implement this logic: - // If x has type A | B, then x.type <: C if - // x.type <: C assuming x has type A, and - // x.type <: C assuming x has type B. - // But it did not work, because derivedRef would always give back the same - // type and cache the denotation. So it ended up copmparing just one branch. - // The code seems to be unncessary for the tests and does not seems to help performance. - // So it is commented out. If we ever need to come back to this, we would have - // to create unchached TermRefs in order to avoid cross talk between the branches. - /* - case OrType(tp11, tp12) => - val sd = tp1.denot.asSingleDenotation - def derivedRef(tp: Type) = - NamedType(tp1.prefix, tp1.name, sd.derivedSingleDenotation(sd.symbol, tp)) - secondTry(OrType.make(derivedRef(tp11), derivedRef(tp12)), tp2) - */ - case TypeBounds(lo1, hi1) => - val gbounds1 = ctx.gadt.bounds(tp1.symbol) - if (gbounds1 != null) - ctdSubTypeWhenFrozen(gbounds1.hi, tp2) || - narrowGADTBounds(tp1, tp2, fromBelow = false) || - thirdTry(tp1, tp2) - else if (lo1 eq hi1) ctdSubType(hi1, tp2) - else thirdTry(tp1, tp2) + private def secondTry(tp1: Type, tp2: Type): Boolean = tp1 match { + case tp1: NamedType => + tp2 match { + case tp2: ThisType if tp2.cls eq tp1.symbol.moduleClass => + isSubType(tp1.prefix, tp2.cls.owner.thisType) case _ => - thirdTry(tp1, tp2) + secondTryNamed(tp1, tp2) } - } - - def thirdTry(tp1: Type, tp2: Type): Boolean = tp2 match { - case tp2: NamedType => - def compareNamed: Boolean = tp2.info match { - case TypeBounds(lo2, hi2) => - val gbounds2 = ctx.gadt.bounds(tp2.symbol) - if (gbounds2 != null) - ctdSubTypeWhenFrozen(tp1, gbounds2.lo) || - narrowGADTBounds(tp2, tp1, fromBelow = true) || - fourthTry(tp1, tp2) - else - ((frozenConstraint || !isCappable(tp1)) && ctdSubType(tp1, lo2) - || fourthTry(tp1, tp2)) - - case _ => - val cls2 = tp2.symbol - if (cls2.isClass) { - val base = tp1.baseTypeRef(cls2) - if (base.exists && (base ne tp1)) return ctdSubType(base, tp2) - if (cls2 == defn.SingletonClass && tp1.isStable) return true + case OrType(tp11, tp12) => + isSubType(tp11, tp2) && isSubType(tp12, tp2) + case tp1: PolyParam => + def comparePolyParam = + tp1 == tp2 || { + if (solvedConstraint && (constraint contains tp1)) isSubType(bounds(tp1).lo, tp2) + else + isSubTypeWhenFrozen(bounds(tp1).hi, tp2) || { + if (isConstrained(tp1)) + addConstraint(tp1, tp2, fromBelow = false) && { + if ((!frozenConstraint) && + (tp2 isRef defn.NothingClass) && + state.isGlobalCommittable) { + def msg = s"!!! instantiated to Nothing: $tp1, constraint = ${constraint.show}" + if (Config.flagInstantiationToNothing) assert(false, msg) + else ctx.log(msg) + } + true + } + else (ctx.mode is Mode.TypevarsMissContext) || thirdTry(tp1, tp2) } - fourthTry(tp1, tp2) - } - compareNamed - case tp2: RefinedType => - def compareRefined: Boolean = { - val tp1w = tp1.widen - val skipped2 = skipMatching(tp1w, tp2) - if (skipped2 eq tp2) { - val name2 = tp2.refinedName - val normalPath = - ctdSubType(tp1, tp2.parent) && - ( name2 == nme.WILDCARD - || hasMatchingMember(name2, tp1, tp2) - || fourthTry(tp1, tp2) - ) - normalPath || - needsEtaLift(tp1, tp2) && tp1.testLifted(tp2.typeParams, isSubType(_, tp2)) - } - else // fast path, in particular for refinements resulting from parameterization. - ctdSubType(tp1, skipped2) && - isSubRefinements(tp1w.asInstanceOf[RefinedType], tp2, skipped2) - } - compareRefined - case OrType(tp21, tp22) => - eitherIsSubType(tp1, tp21, tp1, tp22) || fourthTry(tp1, tp2) - case tp2 @ MethodType(_, formals2) => - def compareMethod = tp1 match { - case tp1 @ MethodType(_, formals1) => - (tp1.signature sameParams tp2.signature) && - (if (Config.newMatch) subsumeParams(formals1, formals2, tp1.isJava, tp2.isJava) - else 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: PolyType => - def comparePoly = tp1 match { - case tp1: PolyType => - (tp1.signature sameParams tp2.signature) && - matchingTypeParams(tp1, tp2) && - isSubType(tp1.resultType, tp2.resultType.subst(tp2, tp1)) - case _ => - false - } - comparePoly - 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, _) => ctdSubType(tp1.resultType, restpe2) - case _ => ctdSubType(tp1.widenExpr, restpe2) - } - compareExpr - case tp2 @ TypeBounds(lo2, hi2) => - def compareTypeBounds = tp1 match { - case tp1 @ TypeBounds(lo1, hi1) => - (tp2.variance > 0 && tp1.variance >= 0 || isSubType(lo2, lo1)) && - (tp2.variance < 0 && tp1.variance <= 0 || ctdSubType(hi1, hi2)) - case tp1: ClassInfo => - val tt = tp1.typeRef - ctdSubType(lo2, tt) && isSubType(tt, hi2) - case _ => - false - } - compareTypeBounds - case ClassInfo(pre2, cls2, _, _, _) => - def compareClassInfo = tp1 match { - case ClassInfo(pre1, cls1, _, _, _) => - (cls1 eq cls2) && isSubType(pre2, pre1) - case _ => - false - } - compareClassInfo - case JavaArrayType(elem2) => - def compareJavaArray = tp1 match { - case JavaArrayType(elem1) => isSubType(elem1, elem2) - case _ => fourthTry(tp1, tp2) - } - compareJavaArray - case _ => - fourthTry(tp1, tp2) - } + comparePolyParam + case tp1: RefinedThis => + tp2 match { + case tp2: RefinedThis if tp1.level == tp2.level => true + case _ => thirdTry(tp1, tp2) + } + case tp1: BoundType => + tp1 == tp2 || thirdTry(tp1, tp2) + case tp1: TypeVar => + (tp1 eq tp2) || isSubType(tp1.underlying, tp2) + case tp1: WildcardType => + def compareWild = tp1.optBounds match { + case TypeBounds(lo, _) => isSubType(lo, tp2) + case _ => true + } + compareWild + case tp1: LazyRef => + isSubType(tp1.ref, tp2) + case tp1: AnnotatedType => + isSubType(tp1.tpe, tp2) + case ErrorType => + true + case _ => + thirdTry(tp1, tp2) + } - def fourthTry(tp1: Type, tp2: Type): Boolean = tp1 match { - case tp1: TypeRef => - tp1.info match { - case TypeBounds(lo1, hi1) => - ctdSubType(hi1, tp2) - case _ => - def isNullable(tp: Type): Boolean = tp.dealias match { - case tp: TypeRef => tp.symbol.isNullableClass - case RefinedType(parent, _) => isNullable(parent) - case AndType(tp1, tp2) => isNullable(tp1) && isNullable(tp2) - case OrType(tp1, tp2) => isNullable(tp1) || isNullable(tp2) - case _ => false - } - (tp1.symbol eq NothingClass) && tp2.isInstanceOf[ValueType] || - (tp1.symbol eq NullClass) && isNullable(tp2) - } - case tp1: SingletonType => - isNewSubType(tp1.underlying.widenExpr, tp2) || { - // if tp2 == p.type and p: q.type then try tp1 <:< q.type as a last effort. - tp2 match { - case tp2: TermRef => - tp2.info match { - case tp2i: TermRef => - ctdSubType(tp1, tp2i) - case ExprType(tp2i: TermRef) if (ctx.phase.id > ctx.gettersPhase.id) => - ctdSubType(tp1, tp2i) - case _ => - false - } - case _ => - false + private def secondTryNamed(tp1: NamedType, tp2: Type): Boolean = tp1.info match { + // There was the following code, which was meant to implement this logic: + // If x has type A | B, then x.type <: C if + // x.type <: C assuming x has type A, and + // x.type <: C assuming x has type B. + // But it did not work, because derivedRef would always give back the same + // type and cache the denotation. So it ended up copmparing just one branch. + // The code seems to be unncessary for the tests and does not seems to help performance. + // So it is commented out. If we ever need to come back to this, we would have + // to create unchached TermRefs in order to avoid cross talk between the branches. + /* + case OrType(tp11, tp12) => + val sd = tp1.denot.asSingleDenotation + def derivedRef(tp: Type) = + NamedType(tp1.prefix, tp1.name, sd.derivedSingleDenotation(sd.symbol, tp)) + secondTry(OrType.make(derivedRef(tp11), derivedRef(tp12)), tp2) + */ + case TypeBounds(lo1, hi1) => + val gbounds1 = ctx.gadt.bounds(tp1.symbol) + if (gbounds1 != null) + isSubTypeWhenFrozen(gbounds1.hi, tp2) || + narrowGADTBounds(tp1, tp2, fromBelow = false) || + thirdTry(tp1, tp2) + else if (lo1 eq hi1) isSubType(hi1, tp2) + else thirdTry(tp1, tp2) + case _ => + thirdTry(tp1, tp2) + } + + private def thirdTry(tp1: Type, tp2: Type): Boolean = tp2 match { + case tp2: NamedType => + def compareNamed: Boolean = tp2.info match { + case TypeBounds(lo2, hi2) => + val gbounds2 = ctx.gadt.bounds(tp2.symbol) + if (gbounds2 != null) + isSubTypeWhenFrozen(tp1, gbounds2.lo) || + narrowGADTBounds(tp2, tp1, fromBelow = true) || + fourthTry(tp1, tp2) + else + ((frozenConstraint || !isCappable(tp1)) && isSubType(tp1, lo2) + || 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) + } + compareNamed + case tp2: RefinedType => + def compareRefined: Boolean = { + val tp1w = tp1.widen + val skipped2 = skipMatching(tp1w, tp2) + if (skipped2 eq tp2) { + val name2 = tp2.refinedName + val normalPath = + isSubType(tp1, tp2.parent) && + ( name2 == nme.WILDCARD + || hasMatchingMember(name2, tp1, tp2) + || fourthTry(tp1, tp2) + ) + normalPath || + needsEtaLift(tp1, tp2) && tp1.testLifted(tp2.typeParams, isSubType(_, tp2)) } - case tp1: RefinedType => - isNewSubType(tp1.parent, tp2) || - needsEtaLift(tp2, tp1) && tp2.testLifted(tp1.typeParams, ctdSubType(tp1, _)) - case AndType(tp11, tp12) => - eitherIsSubType(tp11, tp2, tp12, tp2) - case JavaArrayType(elem1) => - tp2 isRef ObjectClass - case _ => - false - } - - /** Returns true iff either `tp11 <:< tp21` or `tp12 <:< tp22`, trying at the same time - * to keep the constraint as wide as possible. Specifically, 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. - */ - def eitherIsSubType(tp11: Type, tp21: Type, tp12: Type, tp22: Type) = { - val preConstraint = constraint - ctdSubType(tp11, tp21) && { - val leftConstraint = constraint - constraint = preConstraint - if (ctdSubType(tp12, tp22) && !subsumes(leftConstraint, constraint, preConstraint)) - constraint = leftConstraint - true - } || ctdSubType(tp12, tp22) - } + else // fast path, in particular for refinements resulting from parameterization. + isSubType(tp1, skipped2) && + isSubRefinements(tp1w.asInstanceOf[RefinedType], tp2, skipped2) + } + compareRefined + case OrType(tp21, tp22) => + eitherIsSubType(tp1, tp21, tp1, tp22) || fourthTry(tp1, tp2) + case tp2 @ MethodType(_, formals2) => + def compareMethod = tp1 match { + case tp1 @ MethodType(_, formals1) => + (tp1.signature sameParams tp2.signature) && + (if (Config.newMatch) subsumeParams(formals1, formals2, tp1.isJava, tp2.isJava) + else 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: PolyType => + def comparePoly = tp1 match { + case tp1: PolyType => + (tp1.signature sameParams tp2.signature) && + matchingTypeParams(tp1, tp2) && + isSubType(tp1.resultType, tp2.resultType.subst(tp2, tp1)) + case _ => + false + } + comparePoly + 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 || isSubType(lo2, lo1)) && + (tp2.variance < 0 && tp1.variance <= 0 || isSubType(hi1, hi2)) + case tp1: ClassInfo => + val tt = tp1.typeRef + isSubType(lo2, tt) && isSubType(tt, hi2) + case _ => + false + } + compareTypeBounds + case ClassInfo(pre2, cls2, _, _, _) => + def compareClassInfo = tp1 match { + case ClassInfo(pre1, cls1, _, _, _) => + (cls1 eq cls2) && isSubType(pre2, pre1) + case _ => + false + } + compareClassInfo + case JavaArrayType(elem2) => + def compareJavaArray = tp1 match { + case JavaArrayType(elem1) => isSubType(elem1, elem2) + case _ => fourthTry(tp1, tp2) + } + compareJavaArray + case _ => + fourthTry(tp1, tp2) + } - /** Like tp1 <:< tp2, but returns false immediately if we know that - * the case was covered previously during subtyping. - */ - def isNewSubType(tp1: Type, tp2: Type): Boolean = - if (isCovered(tp1) && isCovered(tp2)) { - //println(s"useless subtype: $tp1 <:< $tp2") - false - } else ctdSubType(tp1, tp2) - - def ctdSubTypeWhenFrozen(tp1: Type, tp2: Type): Boolean = { - val saved = frozenConstraint - frozenConstraint = true - try ctdSubType(tp1, tp2) - finally frozenConstraint = saved - } - - 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}") - assert(!ctx.settings.YnoDeepSubtypes.value) - if (Config.traceDeepSubTypeRecursions && !this.isInstanceOf[ExplainingTypeComparer]) - ctx.log(TypeComparer.explained(implicit ctx => ctx.typeComparer.isSubType(tp1, tp2))) + private def fourthTry(tp1: Type, tp2: Type): Boolean = tp1 match { + case tp1: TypeRef => + tp1.info match { + case TypeBounds(lo1, hi1) => + isSubType(hi1, tp2) + case _ => + def isNullable(tp: Type): Boolean = tp.dealias match { + case tp: TypeRef => tp.symbol.isNullableClass + case RefinedType(parent, _) => isNullable(parent) + case AndType(tp1, tp2) => isNullable(tp1) && isNullable(tp2) + case OrType(tp1, tp2) => isNullable(tp1) || isNullable(tp2) + case _ => false + } + (tp1.symbol eq NothingClass) && tp2.isInstanceOf[ValueType] || + (tp1.symbol eq NullClass) && isNullable(tp2) } - val p = (tp1, tp2) - !pendingSubTypes(p) && { - try { - pendingSubTypes += p - firstTry(tp1, tp2) - } finally { - pendingSubTypes -= p + case tp1: SingletonType => + isNewSubType(tp1.underlying.widenExpr, tp2) || { + // if tp2 == p.type and p: q.type then try tp1 <:< q.type as a last effort. + tp2 match { + case tp2: TermRef => + tp2.info match { + case tp2i: TermRef => + isSubType(tp1, tp2i) + case ExprType(tp2i: TermRef) if (ctx.phase.id > ctx.gettersPhase.id) => + isSubType(tp1, tp2i) + case _ => + false + } + case _ => + false } } - } + case tp1: RefinedType => + isNewSubType(tp1.parent, tp2) || + needsEtaLift(tp2, tp1) && tp2.testLifted(tp1.typeParams, isSubType(tp1, _)) + case AndType(tp11, tp12) => + eitherIsSubType(tp11, tp2, tp12, tp2) + case JavaArrayType(elem1) => + tp2 isRef ObjectClass + case _ => + false + } + + /** Returns true iff either `tp11 <:< tp21` or `tp12 <:< tp22`, trying at the same time + * to keep the constraint as wide as possible. Specifically, 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 eitherIsSubType(tp11: Type, tp21: Type, tp12: Type, tp22: Type) = { + val preConstraint = constraint + isSubType(tp11, tp21) && { + val leftConstraint = constraint + constraint = preConstraint + if (isSubType(tp12, tp22) && !subsumes(leftConstraint, constraint, preConstraint)) + constraint = leftConstraint + true + } || isSubType(tp12, tp22) + } - ctdSubType(orig1, orig2) + /** 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) + + private def isSubTypeWhenFrozen(tp1: Type, tp2: Type): Boolean = { + val saved = frozenConstraint + frozenConstraint = true + try isSubType(tp1, tp2) + finally frozenConstraint = saved } - def hasMatchingMember(name: Name, tp1: Type, tp2: RefinedType): Boolean = /*>|>*/ ctx.traceIndented(s"hasMatchingMember($tp1 . $name, ${tp2.refinedInfo}) ${tp1.member(name).info.show}", subtyping) /*<|<*/ { + protected def hasMatchingMember(name: Name, tp1: Type, tp2: RefinedType): Boolean = /*>|>*/ ctx.traceIndented(i"hasMatchingMember($tp1 . $name :? ${tp2.refinedInfo}) ${tp1.member(name).info.show}", subtyping) /*<|<*/ { val saved = skolemsOutstanding try { var base = tp1 @@ -855,8 +836,8 @@ class TypeComparer(initctx: Context) extends DotClass with Skolemization { * the refinement in `tp1` is an alias type. * @return The parent type of `tp2` after skipping the matching refinements. */ - def skipMatching(tp1w: Type, tp2: RefinedType): Type = tp1w match { - case tp1w @ RefinedType(parent1, name1) + private def skipMatching(tp1w: Type, tp2: RefinedType): Type = tp1w match { + case tp1w @ RefinedType(parent1, name1) if name1 == tp2.refinedName && tp1w.refinedInfo.isInstanceOf[TypeAlias] => tp2.parent match { case parent2: RefinedType => skipMatching(parent1, parent2) @@ -871,7 +852,7 @@ class TypeComparer(initctx: Context) extends DotClass with Skolemization { * and their names match the corresponding refinements in `tp2`. * The precondition is established by `skipMatching`. */ - def isSubRefinements(tp1: RefinedType, tp2: RefinedType, limit: Type): Boolean = + private def isSubRefinements(tp1: RefinedType, tp2: RefinedType, limit: Type): Boolean = isSubType(tp1.refinedInfo, tp2.refinedInfo) && ( (tp2.parent eq limit) || isSubRefinements( @@ -908,7 +889,7 @@ class TypeComparer(initctx: Context) extends DotClass with Skolemization { * we should first unroll `tp1` until we hit the type variable and bind the * type variable with (the corresponding type in) `tp2` instead. */ - def isCappable(tp: Type): Boolean = tp match { + private def isCappable(tp: Type): Boolean = tp match { case tp: PolyParam => !solvedConstraint && (constraint contains tp) case tp: TypeProxy => isCappable(tp.underlying) case tp: AndOrType => isCappable(tp.tp1) || isCappable(tp.tp2) @@ -916,7 +897,7 @@ class TypeComparer(initctx: Context) extends DotClass with Skolemization { } /** Does `tp` need to be eta lifted to be comparable to `target`? */ - def needsEtaLift(tp: Type, target: RefinedType): Boolean = { + private def needsEtaLift(tp: Type, target: RefinedType): Boolean = { //default.echo(i"needs eta $tp $target?", { val name = target.refinedName (name.isLambdaArgName || (name eq tpnme.Apply)) && target.isLambda && @@ -924,14 +905,14 @@ class TypeComparer(initctx: Context) extends DotClass with Skolemization { //}) } - def narrowGADTBounds(tr: NamedType, bound: Type, fromBelow: Boolean): Boolean = + private def narrowGADTBounds(tr: NamedType, bound: Type, fromBelow: Boolean): Boolean = ctx.mode.is(Mode.GADTflexible) && { val tparam = tr.symbol val bound1 = deSkolemize(bound, toSuper = fromBelow) println(s"narrow gadt bound of $tparam: ${tparam.info} from ${if (fromBelow) "below" else "above"} to $bound1 ${bound1.isRef(tparam)}") - !bound1.isRef(tparam) && { + !bound1.isRef(tparam) && { val oldBounds = ctx.gadt.bounds(tparam) - val newBounds = + val newBounds = if (fromBelow) TypeBounds(oldBounds.lo | bound1, oldBounds.hi) else TypeBounds(oldBounds.lo, oldBounds.hi & bound1) isSubType(newBounds.lo, newBounds.hi) && @@ -1482,7 +1463,7 @@ class ExplainingTypeComparer(initctx: Context) extends TypeComparer(initctx) { super.isSubType(tp1, tp2) } - override def hasMatchingMember(name: Name, tp1: Type, tp2: RefinedType): Boolean = + 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) } From ee191f38efc9cd0ca05823499c1a8abf7883ff5e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 9 Jan 2015 14:39:36 +0100 Subject: [PATCH 24/72] Made fast path for refined subtype checking configurable. A configuratin now decides whether fast path is taken or not. That way we can more easily test either way if something goes wrong. --- src/dotty/tools/dotc/config/Config.scala | 4 ++++ src/dotty/tools/dotc/core/TypeComparer.scala | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/config/Config.scala b/src/dotty/tools/dotc/config/Config.scala index 61f8bc99ea76..29d5f7922cbd 100644 --- a/src/dotty/tools/dotc/config/Config.scala +++ b/src/dotty/tools/dotc/config/Config.scala @@ -43,7 +43,11 @@ object Config { /** Show subtype traces for all deep subtype recursions */ final val traceDeepSubTypeRecursions = false + /** When explaining subtypes and this flag is set, also show the classes of the compared types. */ final val verboseExplainSubtype = true + + /** If this flag is set, take the fast path when comparing same-named type-aliases and types */ + final val fastPathForRefinedSubtype = true /** When set, use new signature-based matching. * Advantantage of doing so: It's supposed to be faster diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index 2b0391538811..1d27f29a29f8 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -340,7 +340,7 @@ class TypeComparer(initctx: Context) extends DotClass with Skolemization { s"${tp1.show} <:< ${tp2.show}" + (if (ctx.settings.verbose.value) s" ${tp1.getClass} ${tp2.getClass}${if (frozenConstraint) " frozen" else ""}" else "") - def isSubType(tp1: Type, tp2: Type): Boolean = ctx.traceIndented(s"isSubType ${traceInfo(tp1, tp2)}, class1 = ${tp1.getClass}, class2 = ${tp2.getClass}", subtyping) /*<|<*/ { + def isSubType(tp1: Type, tp2: Type): Boolean = ctx.traceIndented(s"isSubType ${traceInfo(tp1, tp2)} ${if (Config.verboseExplainSubtype) s" ${tp1.getClass}, ${tp2.getClass}" else ""}", subtyping) /*<|<*/ { if (tp2 eq NoType) false else if (tp1 eq tp2) true else { @@ -622,7 +622,7 @@ class TypeComparer(initctx: Context) extends DotClass with Skolemization { def compareRefined: Boolean = { val tp1w = tp1.widen val skipped2 = skipMatching(tp1w, tp2) - if (skipped2 eq tp2) { + if ((skipped2 eq tp2) || !Config.fastPathForRefinedSubtype) { val name2 = tp2.refinedName val normalPath = isSubType(tp1, tp2.parent) && From 3c159db66a2a6bb7ead0cec6a945dfe70d03186b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 9 Jan 2015 14:41:21 +0100 Subject: [PATCH 25/72] Simplification for lookupRefined Dropped a member calculation which was unncessessary and which would not work anymore if RefinedThis types lost their RefinedType target field. --- src/dotty/tools/dotc/core/Types.scala | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 21997c5fa688..f877b8e84a85 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -773,11 +773,7 @@ object Types { case pre: RefinedType => if (pre.refinedName ne name) loop(pre.parent) else pre.refinedInfo match { - case TypeAlias(tp) if !pre.refinementRefersToThis => - this.member(name).info match { - case TypeAlias(tp) => tp - case _ => NoType - } + case TypeAlias(tp) if !pre.refinementRefersToThis => tp case _ => loop(pre.parent) } case RefinedThis(rt, _) => From 2ae7f2ab95101f3d68a6e6512704027f01b1b196 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 9 Jan 2015 15:15:01 +0100 Subject: [PATCH 26/72] More aggressive reduction in lookupRefinedThis. See comment for an example what changes. --- src/dotty/tools/dotc/config/Config.scala | 2 +- .../tools/dotc/core/TypeApplications.scala | 25 +++++++++++ src/dotty/tools/dotc/core/Types.scala | 41 ++++++++----------- 3 files changed, 42 insertions(+), 26 deletions(-) diff --git a/src/dotty/tools/dotc/config/Config.scala b/src/dotty/tools/dotc/config/Config.scala index 29d5f7922cbd..317c52633459 100644 --- a/src/dotty/tools/dotc/config/Config.scala +++ b/src/dotty/tools/dotc/config/Config.scala @@ -47,7 +47,7 @@ object Config { final val verboseExplainSubtype = true /** If this flag is set, take the fast path when comparing same-named type-aliases and types */ - final val fastPathForRefinedSubtype = true + final val fastPathForRefinedSubtype = false /** When set, use new signature-based matching. * Advantantage of doing so: It's supposed to be faster diff --git a/src/dotty/tools/dotc/core/TypeApplications.scala b/src/dotty/tools/dotc/core/TypeApplications.scala index 0a3884ded809..7ca2040c6c70 100644 --- a/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/src/dotty/tools/dotc/core/TypeApplications.scala @@ -372,6 +372,31 @@ class TypeApplications(val self: Type) extends AnyVal { case JavaArrayType(elemtp) => elemtp case _ => firstBaseArgInfo(defn.SeqClass) } + + def containsRefinedThis(level: Int)(implicit ctx: Context): Boolean = { + def recur(tp: Type, level: Int): Boolean = tp.stripTypeVar match { + case RefinedThis(rt, `level`) => + true + case tp: NamedType => + tp.info match { + case TypeAlias(alias) => recur(alias, level) + case _ => !tp.symbol.isStatic && recur(tp.prefix, level) + } + case tp: RefinedType => + recur(tp.refinedInfo, level + 1) || + recur(tp.parent, level) + case tp: TypeBounds => + recur(tp.lo, level) || + recur(tp.hi, level) + case tp: AnnotatedType => + recur(tp.underlying, level) + case tp: AndOrType => + recur(tp.tp1, level) || recur(tp.tp2, level) + case _ => + false + } + recur(self, level) + } /** Given a type alias * diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index f877b8e84a85..d12d122d38e4 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -766,14 +766,25 @@ object Types { * P { ... type T = / += / -= U ... } # T * * to just U. Does not perform the reduction if the resulting type would contain - * a reference to the "this" of the current refined type. + * a reference to the "this" of the current refined type. But does follow + * aliases in order to avoid such references. Example: + * + * Lambda$I { type $hk$Arg0 = String, type Apply = this<0>.$hk$Arg0 } # Apply + * + * Here, the refinement for `Apply` has a refined this node, yet dereferencing ones more + * yields `String` as the result of lookupRefined. */ def lookupRefined(name: Name)(implicit ctx: Context): Type = { def loop(pre: Type): Type = pre.stripTypeVar match { case pre: RefinedType => if (pre.refinedName ne name) loop(pre.parent) else pre.refinedInfo match { - case TypeAlias(tp) if !pre.refinementRefersToThis => tp + case TypeAlias(tp) => + if (!pre.refinementRefersToThis) tp + else tp match { + case TypeRef(RefinedThis(_, 0), alias) => lookupRefined(alias) + case _ => NoType + } case _ => loop(pre.parent) } case RefinedThis(rt, _) => @@ -1743,29 +1754,8 @@ object Types { private var refinementRefersToThisKnown: Boolean = false def refinementRefersToThis(implicit ctx: Context): Boolean = { - def recur(tp: Type, level: Int): Boolean = tp.stripTypeVar match { - case RefinedThis(rt, `level`) => - true - case tp: NamedType => - tp.info match { - case TypeAlias(alias) => recur(alias, level) - case _ => !tp.symbol.isStatic && recur(tp.prefix, level) - } - case tp: RefinedType => - recur(tp.refinedInfo, level + 1) || - recur(tp.parent, level) - case tp: TypeBounds => - recur(tp.lo, level) || - recur(tp.hi, level) - case tp: AnnotatedType => - recur(tp.underlying, level) - case tp: AndOrType => - recur(tp.tp1, level) || recur(tp.tp2, level) - case _ => - false - } if (!refinementRefersToThisKnown) { - refinementRefersToThisCache = recur(refinedInfo, 0) + refinementRefersToThisCache = refinedInfo.containsRefinedThis(0) refinementRefersToThisKnown = true } refinementRefersToThisCache @@ -1837,7 +1827,8 @@ object Types { && !parent.isLambda) derivedRefinedType(parent.EtaExpand, refinedName, refinedInfo) else - RefinedType(parent, refinedName, rt => refinedInfo.substThis0(this, RefinedThis(rt, -1))) // !!! TODO: replace with simply `refinedInfo` + if (false) RefinedType(parent, refinedName, refinedInfo) + else RefinedType(parent, refinedName, rt => refinedInfo.substThis0(this, RefinedThis(rt, -1))) // !!! TODO: replace with simply `refinedInfo` } override def equals(that: Any) = that match { From acb64b27c1142b1bf538585347784106fab83767 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 9 Jan 2015 17:07:47 +0100 Subject: [PATCH 27/72] TypeComparer cleanups. Move non-essential code to diagnostics plus some other small cleanups. --- src/dotty/tools/dotc/core/TypeComparer.scala | 110 ++++++++++--------- 1 file changed, 56 insertions(+), 54 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index 1d27f29a29f8..dfa12826e38e 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -126,18 +126,6 @@ class TypeComparer(initctx: Context) extends DotClass with Skolemization { * L & L */ - /** Map that approximates each param in constraint by its lower bound. - * Currently only used for diagnostics. - */ - val approxParams = new TypeMap { - def apply(tp: Type): Type = tp.stripTypeVar match { - case tp: PolyParam if constraint contains tp => - this(constraint.bounds(tp).lo) - case tp => - mapOver(tp) - } - } - /** Test whether the lower bounds of all parameters in this * constraint are a solution to the constraint. */ @@ -336,18 +324,12 @@ class TypeComparer(initctx: Context) extends DotClass with Skolemization { assert(isSatisfiable, constraint.show) } - private def traceInfo(tp1: Type, tp2: Type) = - s"${tp1.show} <:< ${tp2.show}" + - (if (ctx.settings.verbose.value) s" ${tp1.getClass} ${tp2.getClass}${if (frozenConstraint) " frozen" else ""}" else "") - def isSubType(tp1: Type, tp2: Type): Boolean = ctx.traceIndented(s"isSubType ${traceInfo(tp1, tp2)} ${if (Config.verboseExplainSubtype) s" ${tp1.getClass}, ${tp2.getClass}" else ""}", subtyping) /*<|<*/ { if (tp2 eq NoType) false else if (tp1 eq tp2) true else { val saved = constraint val savedSuccessCount = successCount - val savedRLC = Types.reverseLevelCheck // !!! TODO: remove - Types.reverseLevelCheck = false try { recCount = recCount + 1 val result = @@ -356,41 +338,15 @@ class TypeComparer(initctx: Context) extends DotClass with Skolemization { recCount = recCount - 1 if (!result) constraint = saved else if (recCount == 0 && needsGc) state.gc() - - def recordStatistics = { - // Stats.record(s"isSubType ${tp1.show} <:< ${tp2.show}") - totalCount += 1 - if (result) successCount += 1 else successCount = savedSuccessCount - if (recCount == 0) { - Stats.record("successful subType", successCount) - Stats.record("total subType", totalCount) - successCount = 0 - totalCount = 0 - } - } - if (Stats.monitored) recordStatistics - + if (Stats.monitored) recordStatistics(result, savedSuccessCount) result } catch { case NonFatal(ex) => - def showState = { - println(disambiguated(implicit ctx => s"assertion failure for ${tp1.show} <:< ${tp2.show}, frozen = $frozenConstraint")) - def explainPoly(tp: Type) = tp match { - case tp: PolyParam => println(s"polyparam ${tp.show} found in ${tp.binder.show}") - case tp: TypeRef if tp.symbol.exists => println(s"typeref ${tp.show} found in ${tp.symbol.owner.show}") - case tp: TypeVar => println(s"typevar ${tp.show}, origin = ${tp.origin}") - case _ => println(s"${tp.show} is a ${tp.getClass}") - } - explainPoly(tp1) - explainPoly(tp2) - } - if (ex.isInstanceOf[AssertionError]) showState + if (ex.isInstanceOf[AssertionError]) showGoal(tp1, tp2) recCount -= 1 constraint = saved successCount = savedSuccessCount throw ex - } finally { - Types.reverseLevelCheck = savedRLC } } } @@ -803,21 +759,19 @@ class TypeComparer(initctx: Context) extends DotClass with Skolemization { finally frozenConstraint = saved } - protected def hasMatchingMember(name: Name, tp1: Type, tp2: RefinedType): Boolean = /*>|>*/ ctx.traceIndented(i"hasMatchingMember($tp1 . $name :? ${tp2.refinedInfo}) ${tp1.member(name).info.show}", subtyping) /*<|<*/ { + protected def hasMatchingMember(name: Name, tp1: Type, tp2: RefinedType): Boolean = { val saved = skolemsOutstanding try { - var base = tp1 - var rinfo2 = tp2.refinedInfo - if (tp2.refinementRefersToThis) { - base = ensureSingleton(base) - rinfo2 = rinfo2.substRefinedThis(0, base) - } + def rebindNeeded = tp2.refinementRefersToThis + val base = if (rebindNeeded) ensureSingleton(tp1) else tp1 + val rinfo2 = if (rebindNeeded) tp2.refinedInfo.substRefinedThis(0, base) else tp2.refinedInfo def qualifies(m: SingleDenotation) = isSubType(m.info, rinfo2) def memberMatches(mbr: Denotation): Boolean = mbr match { // inlined hasAltWith for performance case mbr: SingleDenotation => qualifies(mbr) case _ => mbr hasAltWith qualifies } - memberMatches(base member name) || + /*>|>*/ ctx.traceIndented(i"hasMatchingMember($tp1 . $name :? ${tp2.refinedInfo}) ${tp1.member(name).info.show} $rinfo2", subtyping) /*<|<*/ { + memberMatches(base member name) || tp1.isInstanceOf[SingletonType] && { // special case for situations like: // foo <: C { type T = foo.T } @@ -827,6 +781,7 @@ class TypeComparer(initctx: Context) extends DotClass with Skolemization { case _ => false } } + } } finally skolemsOutstanding = saved } @@ -1421,8 +1376,55 @@ class TypeComparer(initctx: Context) extends DotClass with Skolemization { /** 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) s" ${tp1.getClass} ${tp2.getClass}${if (frozenConstraint) " frozen" else ""}" else "") + + /** Map that approximates each param in constraint by its lower bound. + * Currently only used for diagnostics. + */ + val approxParams = new TypeMap { + def apply(tp: Type): Type = tp.stripTypeVar match { + case tp: PolyParam if constraint contains tp => + this(constraint.bounds(tp).lo) + case tp => + mapOver(tp) + } + } + + /** Show subtype goal that led to an assertion failure */ + def showGoal(tp1: Type, tp2: Type) = { + println(disambiguated(implicit ctx => s"assertion failure for ${tp1.show} <:< ${tp2.show}, frozen = $frozenConstraint")) + def explainPoly(tp: Type) = tp match { + case tp: PolyParam => println(s"polyparam ${tp.show} found in ${tp.binder.show}") + case tp: TypeRef if tp.symbol.exists => println(s"typeref ${tp.show} found in ${tp.symbol.owner.show}") + case tp: TypeVar => println(s"typevar ${tp.show}, origin = ${tp.origin}") + case _ => println(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 { From 710e40e991db4c1c519cfea1d3112b558ad044e9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 9 Jan 2015 17:21:38 +0100 Subject: [PATCH 28/72] Made refinement subtype fastpath insensitive to RefineedThis#binder. Since the binder field in RefinedTypes should not be significant for subtyping, we need to substitute RefinedThis types away before comparing refinements. But this substitution is only done in the slow path. The fix falls back to the slow path if a refinement refers to the refined type via a refined this. --- src/dotty/tools/dotc/config/Config.scala | 2 +- src/dotty/tools/dotc/core/TypeComparer.scala | 17 ++++++++++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/dotty/tools/dotc/config/Config.scala b/src/dotty/tools/dotc/config/Config.scala index 317c52633459..29d5f7922cbd 100644 --- a/src/dotty/tools/dotc/config/Config.scala +++ b/src/dotty/tools/dotc/config/Config.scala @@ -47,7 +47,7 @@ object Config { final val verboseExplainSubtype = true /** If this flag is set, take the fast path when comparing same-named type-aliases and types */ - final val fastPathForRefinedSubtype = false + final val fastPathForRefinedSubtype = true /** When set, use new signature-based matching. * Advantantage of doing so: It's supposed to be faster diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index dfa12826e38e..83068ab31199 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -787,13 +787,19 @@ class TypeComparer(initctx: Context) extends DotClass with Skolemization { } /** Skip refinements in `tp2` which match corresponding refinements in `tp1`. - * "Match" means: They appear in the same order, refine the same names, and - * the refinement in `tp1` is an alias type. + * "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(tp1w: Type, tp2: RefinedType): Type = tp1w match { - case tp1w @ RefinedType(parent1, name1) - if name1 == tp2.refinedName && tp1w.refinedInfo.isInstanceOf[TypeAlias] => + private def skipMatching(tp1: Type, tp2: RefinedType): Type = tp1 match { + case tp1 @ RefinedType(parent1, name1) + if name1 == tp2.refinedName && + tp1.refinedInfo.isInstanceOf[TypeAlias] && + !tp2.refinementRefersToThis && + !tp1.refinementRefersToThis => tp2.parent match { case parent2: RefinedType => skipMatching(parent1, parent2) case parent2 => parent2 @@ -805,6 +811,7 @@ class TypeComparer(initctx: Context) extends DotClass with Skolemization { * 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 = From 4429243278ea671ccaf8344f4b65519fe6800a47 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 10 Jan 2015 14:55:38 +0100 Subject: [PATCH 29/72] Removed some checks Checks are no longer interesting because we will not to migrate to RefinedThis(level) scheme after all. --- src/dotty/tools/dotc/core/TypeApplications.scala | 2 +- src/dotty/tools/dotc/core/Types.scala | 11 ----------- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeApplications.scala b/src/dotty/tools/dotc/core/TypeApplications.scala index 7ca2040c6c70..e71226b68714 100644 --- a/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/src/dotty/tools/dotc/core/TypeApplications.scala @@ -507,7 +507,7 @@ class TypeApplications(val self: Type) extends AnyVal { * LambdaXYZ { type Apply = B[$hkArg$0, ..., $hkArg$n] } * { type $hkArg$0 = T1; ...; type $hkArg$n = Tn } * - * satisfies predicate `p`. Try base types in the order of ther occurrence in `baseClasses`. + * satisfies predicate `p`. Try base types in the order of their occurrence in `baseClasses`. * A type parameter matches a varianve V if it has V as its variance or if V == 0. */ def testLifted(tparams: List[Symbol], p: Type => Boolean)(implicit ctx: Context): Boolean = { diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index d12d122d38e4..f2329d284241 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -442,11 +442,6 @@ object Types { if (tp.refinementRefersToThis) tp.refinedInfo.substRefinedThis(0, pre) else tp.refinedInfo } - if (Types.goRefinedCheck) { - val rinfo0 = tp.refinedInfo.substThis0(tp, pre) - if ((rinfo0 ne rinfo) && (rinfo0.show != rinfo.show)) - println(s"findMember discrepancy for $tp , $name, pre = $pre, old = $rinfo0, new = $rinfo") - } if (name.isTypeName) { // simplified case that runs more efficiently val jointInfo = if (rinfo.isAlias) rinfo else pdenot.info & rinfo pdenot.asSingleDenotation.derivedSingleDenotation(pdenot.symbol, jointInfo) @@ -1774,9 +1769,6 @@ object Types { } } if (rt eq RefinedType.this) assert(l == level, RefinedType.this) - if (Types.reverseLevelCheck && l == level) - assert(dominates(rt, RefinedType.this) || dominates(RefinedType.this, rt), - RefinedType.this) case tp: RefinedType => level += 1 apply(x, tp.refinedInfo) @@ -3050,9 +3042,6 @@ object Types { var debugTrace = false - var reverseLevelCheck = false - var goRefinedCheck = false - val watchList = List[String]( ) map (_.toTypeName) From 5f35b11ceb228e7a803263490f2d8e8a22ee2fe6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 10 Jan 2015 14:41:58 +0100 Subject: [PATCH 30/72] Split off ConstraintHandling into separate trait. --- .../tools/dotc/core/ConstraintHandling.scala | 275 ++++++++++++++++++ src/dotty/tools/dotc/core/Skolemization.scala | 21 +- src/dotty/tools/dotc/core/TypeComparer.scala | 259 +---------------- src/dotty/tools/dotc/core/Types.scala | 2 + 4 files changed, 285 insertions(+), 272 deletions(-) create mode 100644 src/dotty/tools/dotc/core/ConstraintHandling.scala diff --git a/src/dotty/tools/dotc/core/ConstraintHandling.scala b/src/dotty/tools/dotc/core/ConstraintHandling.scala new file mode 100644 index 000000000000..98649234f4de --- /dev/null +++ b/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -0,0 +1,275 @@ +package dotty.tools +package dotc +package core + +import Types._, Contexts._, Symbols._ +import Decorators._ +import config.Config +import config.Printers._ + +/** Methods for adding constraints and solving them. + * + * Constraints are required to be in normalized form. This means + * (1) if P <: Q in C then also Q >: P in C + * (2) if P r Q in C and Q r R in C then also P r R in C, where r is <: or :> + * + * "P <: Q in C" means here: There is a constraint P <: H[Q], + * where H is the multi-hole context given by: + * + * H = [] + * H & T + * T & H + * H | H + * + * (the idea is that a parameter Q in a H context is guaranteed to be a supertype of P). + * + * "P >: Q in C" means: There is a constraint P >: L[Q], + * where L is the multi-hole context given by: + * + * L = [] + * L | T + * T | L + * L & L + */ +trait ConstraintHandling { + + implicit val ctx: Context + + def isSubType(tp1: Type, tp2: Type): Boolean + def deSkolemize(tp: Type, toSuper: Boolean): Type + + val state: TyperState + import state.constraint + + /** If the constraint is frozen we cannot add new bounds to the constraint. */ + protected var frozenConstraint = false + + /** If the constraint is ignored, subtype checks only take into account + * declared bounds of PolyParams. Used when forming unions and intersectons + * of constraint bounds + */ + private var ignoreConstraint = false + + private def ignoringConstraint[T](op: => T): T = { + val savedIgnore = ignoreConstraint + val savedFrozen = frozenConstraint + ignoreConstraint = true + frozenConstraint = true + try op + finally { + ignoreConstraint = savedIgnore + frozenConstraint = savedFrozen + } + } + + /** The current bounds of type parameter `param` */ + def bounds(param: PolyParam): TypeBounds = constraint at param match { + case bounds: TypeBounds if !ignoreConstraint => bounds + case _ => param.binder.paramBounds(param.paramNum) + } + + /** Compare a solution of the constraint instead of the constrained parameters. + * The solution maps every parameter to its lower bound. + */ + protected var solvedConstraint = false + + /** The parameters currently being constrained by addConstraint */ + private var pendingParams: Set[PolyParam] = Set() + + /** Make p2 = p1, transfer all bounds of p2 to p1 */ + private def unify(p1: PolyParam, p2: PolyParam): Boolean = { + constr.println(s"unifying $p1 $p2") + val constraint1 = constraint.unify(p1, p2) + val bounds = constraint1.bounds(p1) + isSubType(bounds.lo, bounds.hi) && { constraint = constraint1; true } + } + + /** If current constraint set is not frozen, add the constraint + * + * param >: bound if fromBelow is true + * param <: bound otherwise + * + * to the bounds of `param`. If `bound` is itself a constrained parameter, also + * add the dual constraint to `bound`. + * @pre `param` is in the constraint's domain + * @return Whether the augmented constraint is still satisfiable. + */ + def addConstraint(param: PolyParam, bound0: Type, fromBelow: Boolean): Boolean = { + + /** Add bidirectional constraint. If new constraint implies 'A <: B' we also + * make sure 'B >: A' gets added and vice versa. Furthermore, if the constraint + * implies 'A <: B <: A', A and B get unified. + */ + def addc(param: PolyParam, bound: Type, fromBelow: Boolean): Boolean = + constraint.bounds(param) match { + case TypeBounds(plo: PolyParam, phi) if (plo eq phi) && constraint.contains(plo) => + addc(plo, bound, fromBelow) + case pbounds0 => + bound match { + case bound: PolyParam if constraint contains bound => + val bbounds0 @ TypeBounds(lo, hi) = constraint.bounds(bound) + if (lo eq hi) + addc(param, lo, fromBelow) + else if (param == bound) + true + else if (fromBelow && param.occursIn(lo, fromBelow = true)) + unify(param, bound) + else if (!fromBelow && param.occursIn(hi, fromBelow = false)) + unify(bound, param) + else { + val pbounds = prepare(param, bound, fromBelow) + val bbounds = prepare(bound, param, !fromBelow) + pbounds.exists && bbounds.exists && { + install(param, pbounds.bounds, pbounds0) + install(bound, bbounds.bounds, bbounds0) + true + } + } + case bound: AndOrType if fromBelow != bound.isAnd => + addc(param, bound.tp1, fromBelow) && + addc(param, bound.tp2, fromBelow) + case bound: WildcardType => + true + case bound => // !!! remove to keep the originals + val pbounds = prepare(param, bound, fromBelow) + pbounds.exists && { + install(param, pbounds.bounds, pbounds0) + true + } + } + } + + /** Install bounds for param */ + def install(param: PolyParam, newBounds: TypeBounds, oldBounds: TypeBounds): Unit = { + val curBounds = constraint.bounds(param) + constraint = constraint.updated(param, newBounds) + if (curBounds ne oldBounds) { + // In this case the bounds were updated previously by a recursive isSubType in + // the satisfiability check of prepare. Reapply the previously added bounds, but + // go through a full addConstraint in order to eliminate any cyclic dependencies + // via unification. + if (!ignoringConstraint(isSubType(curBounds.lo, newBounds.lo))) + addConstraint(param, curBounds.lo, fromBelow) + if (!ignoringConstraint(isSubType(newBounds.hi, curBounds.hi))) + addConstraint(param, curBounds.hi, !fromBelow) + } + } + + /** Compute new bounds for `param` and check whether they are + * satisfiable. The check might in turn trigger other additions to the constraint. + * @return The new bounds for `param` (which are not installed yet), or + * NoType, if the new constraint would not be satisfiable. + */ + def prepare(param: PolyParam, bound: Type, fromBelow: Boolean): Type = { + constr.println(s"prepare ${param.show} ${if (fromBelow) ">:>" else "<:<"} ${bound.show}") + val oldBounds = constraint.bounds(param) + val newBounds = ignoringConstraint { + if (fromBelow) oldBounds.derivedTypeBounds(oldBounds.lo | bound, oldBounds.hi) + else oldBounds.derivedTypeBounds(oldBounds.lo, oldBounds.hi & bound) + } + val ok = + (param == bound) || + (oldBounds eq newBounds) || + { + if (pendingParams contains param) { + // Why the pendingParams test? It is possible that recursive subtype invocations + // come back with another constraint for `param`. An example came up when compiling + // ElimRepeated where we got the constraint + // + // Coll <: IterableLike[Tree, Coll] + // + // and added + // + // List[Tree] <: Coll + // + // The recursive bounds test is then + // + // List[Tree] <: IterableLike[Tree, Coll] + // + // and because of the F-bounded polymorphism in the supertype of List, + // i.e. List[T] <: IterableLike[T, List[T]], this leads again to + // + // List[Tree] <: Coll + // + // If a parameter is already pending, we avoid revisiting it here. + // Instead we combine the bounds computed here with the originally + // computed bounds when installing the original type. + constr.println(i"deferred bounds: $param $newBounds") + true + } else { + pendingParams += param + try isSubType(newBounds.lo, newBounds.hi) + finally pendingParams -= param + } + } + if (ok) newBounds else NoType + } + + val bound = deSkolemize(bound0, toSuper = fromBelow).dealias.stripTypeVar + def description = s"${param.show} ${if (fromBelow) ">:>" else "<:<"} ${bound.show} to ${constraint.show}" + constr.println(s"adding $description") + val res = addc(param, bound, fromBelow) + constr.println(s"added $description") + if (Config.checkConstraintsNonCyclicTrans) constraint.checkNonCyclicTrans() + res + } + + def isConstrained(param: PolyParam): Boolean = + !frozenConstraint && !solvedConstraint && (constraint contains param) + + /** Test whether the lower bounds of all parameters in this + * constraint are a solution to the constraint. + */ + def isSatisfiable: Boolean = { + val saved = solvedConstraint + solvedConstraint = true + try + constraint.forallParams { param => + val TypeBounds(lo, hi) = constraint.at(param) + isSubType(lo, hi) || { + ctx.log(i"sub fail $lo <:< $hi") + ctx.log(i"approximated = ${approxParams(lo)} <:< ${approxParams(hi)}") + false + } + } + finally solvedConstraint = saved + } + + /** Solve constraint set for given type parameter `param`. + * If `fromBelow` is true the parameter is approximated by its lower bound, + * otherwise it is approximated by its upper bound. However, any occurrences + * of the parameter in a refinement somewhere in the bound are removed. + * (Such occurrences can arise for F-bounded types). + * The constraint is left unchanged. + * @return the instantiating type + * @pre `param` is in the constraint's domain. + */ + def approximation(param: PolyParam, fromBelow: Boolean): Type = { + val avoidParam = new TypeMap { + override def stopAtStatic = true + def apply(tp: Type) = mapOver { + tp match { + case tp: RefinedType if param occursIn tp.refinedInfo => tp.parent + case _ => tp + } + } + } + val bounds = constraint.bounds(param) + val bound = if (fromBelow) bounds.lo else bounds.hi + val inst = avoidParam(bound) + typr.println(s"approx ${param.show}, from below = $fromBelow, bound = ${bound.show}, inst = ${inst.show}") + inst + } + + /** Map that approximates each param in constraint by its lower bound. + * Currently only used for diagnostics. + */ + def approxParams = new TypeMap { // !!! Dotty problem: Turn this def into a val => -Ycheck:mix fails + def apply(tp: Type): Type = tp.stripTypeVar match { + case tp: PolyParam if constraint contains tp => + this(constraint.bounds(tp).lo) + case tp => + mapOver(tp) + } + } +} diff --git a/src/dotty/tools/dotc/core/Skolemization.scala b/src/dotty/tools/dotc/core/Skolemization.scala index 592ec860b4e2..320150c4458d 100644 --- a/src/dotty/tools/dotc/core/Skolemization.scala +++ b/src/dotty/tools/dotc/core/Skolemization.scala @@ -1,14 +1,16 @@ package dotty.tools.dotc package core -import Symbols._, Types._, Contexts._, Decorators._, NameOps._, StdNames._ +import Symbols._, Types._, Contexts._ import collection.mutable trait Skolemization { + implicit val ctx: Context + protected var skolemsOutstanding = false - def ensureSingleton(tp: Type)(implicit ctx: Context): SingletonType = tp.stripTypeVar match { + def ensureSingleton(tp: Type): SingletonType = tp.stripTypeVar match { case tp: SingletonType => tp case tp: ValueType => @@ -22,10 +24,10 @@ trait Skolemization { * @param toSuper if true, return the smallest supertype of `tp` with this property * e;se return the largest subtype. */ - final def deSkolemize(tp: Type, toSuper: Boolean)(implicit ctx: Context): Type = + final def deSkolemize(tp: Type, toSuper: Boolean): Type = if (skolemsOutstanding) deSkolemize(tp, if (toSuper) 1 else -1, Set()) else tp - private def deSkolemize(tp: Type, variance: Int, seen: Set[Symbol])(implicit ctx: Context): Type = + private def deSkolemize(tp: Type, variance: Int, seen: Set[Symbol]): Type = ctx.traceIndented(s"deskolemize $tp, variance = $variance, seen = $seen = ") { def approx(lo: Type = defn.NothingType, hi: Type = defn.AnyType, newSeen: Set[Symbol] = seen) = if (variance == 0) NoType @@ -94,17 +96,8 @@ trait Skolemization { deSkolemizeMap.mapOver(tp, variance, seen) } } - - private var deSkolemizeMapCache: DeSkolemizeMap = null - - private def deSkolemizeMap(implicit ctx: Context) = { - if (deSkolemizeMapCache == null || deSkolemizeMapCache.definingCtx != ctx) - deSkolemizeMapCache = new DeSkolemizeMap - deSkolemizeMapCache - } - private class DeSkolemizeMap(implicit ctx: Context) extends TypeMap { - def definingCtx = ctx + object deSkolemizeMap extends TypeMap { private var seen: Set[Symbol] = _ def apply(tp: Type) = deSkolemize(tp, variance, seen) def mapOver(tp: Type, variance: Int, seen: Set[Symbol]) = { diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index 83068ab31199..8717a45df244 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -16,7 +16,7 @@ import scala.util.control.NonFatal /** Provides methods to compare types. */ -class TypeComparer(initctx: Context) extends DotClass with Skolemization { +class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling with Skolemization { implicit val ctx: Context = initctx val state = ctx.typerState @@ -25,35 +25,6 @@ class TypeComparer(initctx: Context) extends DotClass with Skolemization { private var pendingSubTypes: mutable.Set[(Type, Type)] = null private var recCount = 0 - /** If the constraint is frozen we cannot add new bounds to the constraint. */ - protected var frozenConstraint = false - - /** If the constraint is ignored, subtype checks only take into account - * declared bounds of PolyParams. Used when forming unions and intersectons - * of constraint bounds - */ - private var ignoreConstraint = false - - private def ignoringConstraint[T](op: => T): T = { - val savedIgnore = ignoreConstraint - val savedFrozen = frozenConstraint - ignoreConstraint = true - frozenConstraint = true - try op - finally { - ignoreConstraint = savedIgnore - frozenConstraint = savedFrozen - } - } - - /** Compare a solution of the constraint instead of the constrained parameters. - * The solution maps every parameter to its lower bound. - */ - protected var solvedConstraint = false - - /** The parameters currently being constrained by addConstraint */ - private var pendingParams: Set[PolyParam] = Set() - private var needsGc = false /** Is a subtype check in progress? In that case we may not @@ -101,216 +72,6 @@ class TypeComparer(initctx: Context) extends DotClass with Skolemization { myAnyType } - /* Constraint handling: - * - * Constraints are required to be in normalized form. This means - * (1) if P <: Q in C then also Q >: P in C - * (2) if P r Q in C and Q r R in C then also P r R in C, where r is <: or :> - * - * "P <: Q in C" means here: There is a constraint P <: H[Q], - * where H is the multi-hole context given by: - * - * H = [] - * H & T - * T & H - * H | H - * - * (the idea is that a parameter Q in a H context is guaranteed to be a supertype of P). - * - * "P >: Q in C" means: There is a constraint P >: L[Q], - * where L is the multi-hole context given by: - * - * L = [] - * L | T - * T | L - * L & L - */ - - /** Test whether the lower bounds of all parameters in this - * constraint are a solution to the constraint. - */ - def isSatisfiable: Boolean = { - val saved = solvedConstraint - solvedConstraint = true - try - constraint.forallParams { param => - val TypeBounds(lo, hi) = constraint.at(param) - isSubType(lo, hi) || { - ctx.log(i"sub fail $lo <:< $hi") - ctx.log(i"approximated = ${approxParams(lo)} <:< ${approxParams(hi)}") - false - } - } - finally solvedConstraint = saved - } - - /** Make p2 = p1, transfer all bounds of p2 to p1 */ - private def unify(p1: PolyParam, p2: PolyParam): Boolean = { - constr.println(s"unifying $p1 $p2") - val constraint1 = constraint.unify(p1, p2) - val bounds = constraint1.bounds(p1) - isSubType(bounds.lo, bounds.hi) && { constraint = constraint1; true } - } - - /** If current constraint set is not frozen, add the constraint - * - * param >: bound if fromBelow is true - * param <: bound otherwise - * - * to the bounds of `param`. If `bound` is itself a constrained parameter, also - * add the dual constraint to `bound`. - * @pre `param` is in the constraint's domain - * @return Whether the augmented constraint is still satisfiable. - */ - def addConstraint(param: PolyParam, bound0: Type, fromBelow: Boolean): Boolean = { - - /** Add bidirectional constraint. If new constraint implies 'A <: B' we also - * make sure 'B >: A' gets added and vice versa. Furthermore, if the constraint - * implies 'A <: B <: A', A and B get unified. - */ - def addc(param: PolyParam, bound: Type, fromBelow: Boolean): Boolean = - constraint.bounds(param) match { - case TypeBounds(plo: PolyParam, phi) if (plo eq phi) && constraint.contains(plo) => - addc(plo, bound, fromBelow) - case pbounds0 => - bound match { - case bound: PolyParam if constraint contains bound => - val bbounds0 @ TypeBounds(lo, hi) = constraint.bounds(bound) - if (lo eq hi) - addc(param, lo, fromBelow) - else if (param == bound) - true - else if (fromBelow && param.occursIn(lo, fromBelow = true)) - unify(param, bound) - else if (!fromBelow && param.occursIn(hi, fromBelow = false)) - unify(bound, param) - else { - val pbounds = prepare(param, bound, fromBelow) - val bbounds = prepare(bound, param, !fromBelow) - pbounds.exists && bbounds.exists && { - install(param, pbounds.bounds, pbounds0) - install(bound, bbounds.bounds, bbounds0) - true - } - } - case bound: AndOrType if fromBelow != bound.isAnd => - addc(param, bound.tp1, fromBelow) && - addc(param, bound.tp2, fromBelow) - case bound: WildcardType => - true - case bound => // !!! remove to keep the originals - val pbounds = prepare(param, bound, fromBelow) - pbounds.exists && { - install(param, pbounds.bounds, pbounds0) - true - } - } - } - - /** Install bounds for param */ - def install(param: PolyParam, newBounds: TypeBounds, oldBounds: TypeBounds): Unit = { - val curBounds = constraint.bounds(param) - constraint = constraint.updated(param, newBounds) - if (curBounds ne oldBounds) { - // In this case the bounds were updated previously by a recursive isSubType in - // the satisfiability check of prepare. Reapply the previously added bounds, but - // go through a full addConstraint in order to eliminate any cyclic dependencies - // via unification. - if (!ignoringConstraint(isSubType(curBounds.lo, newBounds.lo))) - addConstraint(param, curBounds.lo, fromBelow) - if (!ignoringConstraint(isSubType(newBounds.hi, curBounds.hi))) - addConstraint(param, curBounds.hi, !fromBelow) - } - } - - /** Compute new bounds for `param` and check whether they are - * satisfiable. The check might in turn trigger other additions to the constraint. - * @return The new bounds for `param` (which are not installed yet), or - * NoType, if the new constraint would not be satisfiable. - */ - def prepare(param: PolyParam, bound: Type, fromBelow: Boolean): Type = { - constr.println(s"prepare ${param.show} ${if (fromBelow) ">:>" else "<:<"} ${bound.show}") - val oldBounds = constraint.bounds(param) - val newBounds = ignoringConstraint { - if (fromBelow) oldBounds.derivedTypeBounds(oldBounds.lo | bound, oldBounds.hi) - else oldBounds.derivedTypeBounds(oldBounds.lo, oldBounds.hi & bound) - } - val ok = - (param == bound) || - (oldBounds eq newBounds) || - { - if (pendingParams contains param) { - // Why the pendingParams test? It is possible that recursive subtype invocations - // come back with another constraint for `param`. An example came up when compiling - // ElimRepeated where we got the constraint - // - // Coll <: IterableLike[Tree, Coll] - // - // and added - // - // List[Tree] <: Coll - // - // The recursive bounds test is then - // - // List[Tree] <: IterableLike[Tree, Coll] - // - // and because of the F-bounded polymorphism in the supertype of List, - // i.e. List[T] <: IterableLike[T, List[T]], this leads again to - // - // List[Tree] <: Coll - // - // If a parameter is already pending, we avoid revisiting it here. - // Instead we combine the bounds computed here with the originally - // computed bounds when installing the original type. - constr.println(i"deferred bounds: $param $newBounds") - true - } else { - pendingParams += param - try isSubType(newBounds.lo, newBounds.hi) - finally pendingParams -= param - } - } - if (ok) newBounds else NoType - } - - val bound = deSkolemize(bound0, toSuper = fromBelow).dealias.stripTypeVar - def description = s"${param.show} ${if (fromBelow) ">:>" else "<:<"} ${bound.show} to ${constraint.show}" - constr.println(s"adding $description") - val res = addc(param, bound, fromBelow) - constr.println(s"added $description") - if (Config.checkConstraintsNonCyclicTrans) constraint.checkNonCyclicTrans() - res - } - - def isConstrained(param: PolyParam): Boolean = - !frozenConstraint && !solvedConstraint && (constraint contains param) - - /** Solve constraint set for given type parameter `param`. - * If `fromBelow` is true the parameter is approximated by its lower bound, - * otherwise it is approximated by its upper bound. However, any occurrences - * of the parameter in a refinement somewhere in the bound are removed. - * (Such occurrences can arise for F-bounded types). - * The constraint is left unchanged. - * @return the instantiating type - * @pre `param` is in the constraint's domain. - */ - def approximation(param: PolyParam, fromBelow: Boolean): Type = { - val avoidParam = new TypeMap { - override def stopAtStatic = true - def apply(tp: Type) = mapOver { - tp match { - case tp: RefinedType if param occursIn tp.refinedInfo => tp.parent - case _ => tp - } - } - } - val bounds = constraint.bounds(param) - val bound = if (fromBelow) bounds.lo else bounds.hi - val inst = avoidParam(bound) - typr.println(s"approx ${param.show}, from below = $fromBelow, bound = ${bound.show}, inst = ${inst.show}") - inst - } - // Subtype testing `<:<` def topLevelSubType(tp1: Type, tp2: Type): Boolean = { @@ -833,12 +594,6 @@ class TypeComparer(initctx: Context) extends DotClass with Skolemization { case _ => false } - /** The current bounds of type parameter `param` */ - def bounds(param: PolyParam): TypeBounds = constraint at param match { - case bounds: TypeBounds if !ignoreConstraint => bounds - case _ => param.binder.paramBounds(param.paramNum) - } - /** Defer constraining type variables when compared against prototypes */ def isMatchedByProto(proto: ProtoType, tp: Type) = tp.stripTypeVar match { case tp: PolyParam if !solvedConstraint && (constraint contains tp) => true @@ -1392,18 +1147,6 @@ class TypeComparer(initctx: Context) extends DotClass with Skolemization { s"${tp1.show} <:< ${tp2.show}" + (if (ctx.settings.verbose.value) s" ${tp1.getClass} ${tp2.getClass}${if (frozenConstraint) " frozen" else ""}" else "") - /** Map that approximates each param in constraint by its lower bound. - * Currently only used for diagnostics. - */ - val approxParams = new TypeMap { - def apply(tp: Type): Type = tp.stripTypeVar match { - case tp: PolyParam if constraint contains tp => - this(constraint.bounds(tp).lo) - case tp => - mapOver(tp) - } - } - /** Show subtype goal that led to an assertion failure */ def showGoal(tp1: Type, tp2: Type) = { println(disambiguated(implicit ctx => s"assertion failure for ${tp1.show} <:< ${tp2.show}, frozen = $frozenConstraint")) diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index f2329d284241..d64e7340e426 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -2217,6 +2217,8 @@ object Types { } /** A reference to the `this` of an enclosing refined type. + * @param binder The refinement type referred to the RefinedThis when + * it was created. * @param level The number of enclosing refined types between * the `this` reference and its target. */ From 97aced07f273a31be69fd771a4e900a8e0cfa43a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 10 Jan 2015 17:47:55 +0100 Subject: [PATCH 31/72] Reverting the idea that RefinedThis types take levels. In the end, this did not buy us anything. What matters is that - we can reliably identify RefinedThis types pointing to a given refinement type. Making sure that the `binder` field of q RefinedThis type in a refinedInfo is always the containing refined type is good enough for that. - we take care to rebind RefinedThis types in isSubType. This was leaky before, is handled now better in the new isSubType. So, in the end, adding a level was a needless complication. Also, as a next step we should be able to identify skolem types and RefinedThis types. --- src/dotty/tools/dotc/core/Substituters.scala | 71 +------------------ .../tools/dotc/core/TypeApplications.scala | 39 +++++----- src/dotty/tools/dotc/core/TypeComparer.scala | 6 +- src/dotty/tools/dotc/core/TypeOps.scala | 15 ---- src/dotty/tools/dotc/core/Types.scala | 63 +++------------- .../tools/dotc/core/pickling/UnPickler.scala | 8 ++- .../tools/dotc/printing/PlainPrinter.scala | 7 +- src/dotty/tools/dotc/typer/Typer.scala | 2 +- tests/pos/Patterns.scala | 2 +- 9 files changed, 49 insertions(+), 164 deletions(-) diff --git a/src/dotty/tools/dotc/core/Substituters.scala b/src/dotty/tools/dotc/core/Substituters.scala index 9fd01ab564cb..76264519f7cb 100644 --- a/src/dotty/tools/dotc/core/Substituters.scala +++ b/src/dotty/tools/dotc/core/Substituters.scala @@ -183,13 +183,8 @@ trait Substituters { this: Context => // the target type from RefinedThis. final def substThis0(tp: Type, from: RefinedType, to: Type, theMap: SubstRefinedThisMap0): Type = tp match { - case tp @ RefinedThis(rt, level) => - if (rt eq from) - to match { // !!! TODO drop - case RefinedThis(rt1, -1) => RefinedThis(rt1, level) - case _ => to - } - else tp + case tp @ RefinedThis(rt) => + if (rt eq from) to else tp case tp: NamedType => if (tp.currentSymbol.isStatic) tp else tp.derivedSelect(substThis0(tp.prefix, from, to, theMap)) @@ -202,29 +197,6 @@ trait Substituters { this: Context => case _ => (if (theMap != null) theMap else new SubstRefinedThisMap0(from, to)) .mapOver(tp) - } - - final def substRefinedThis(tp: Type, level: Int, to: Type, theMap: SubstRefinedThisMap): Type = - tp match { - case tp @ RefinedThis(rt, l) if l == level => - to - case tp: NamedType => - if (tp.currentSymbol.isStatic) tp - else tp.derivedSelect(substRefinedThis(tp.prefix, level, to, theMap)) - case _: ThisType | _: BoundType | NoPrefix => - tp - case tp: RefinedType => - tp.derivedRefinedType( - substRefinedThis(tp.parent, level, to, theMap), tp.refinedName, - substRefinedThis(tp.refinedInfo, level + 1, to, theMap)) - case tp: TypeAlias => - tp.derivedTypeAlias(substRefinedThis(tp.alias, level, to, theMap)) - case _ => - val m = if (theMap != null) theMap else new SubstRefinedThisMap(to) - val saved = m.level - m.level = level - try m.mapOver(tp) - finally m.level = saved } final def substParam(tp: Type, from: ParamType, to: Type, theMap: SubstParamMap): Type = @@ -300,11 +272,6 @@ trait Substituters { this: Context => def apply(tp: Type): Type = substThis0(tp, from, to, this) } - final class SubstRefinedThisMap(to: Type) extends DeepTypeMap { - var level: Int = 0 - def apply(tp: Type): Type = substRefinedThis(tp, level, to, this) - } - final class SubstParamMap(from: ParamType, to: Type) extends DeepTypeMap { def apply(tp: Type) = substParam(tp, from, to, this) } @@ -312,38 +279,4 @@ trait Substituters { this: Context => final class SubstParamsMap(from: BindingType, to: List[Type]) extends DeepTypeMap { def apply(tp: Type) = substParams(tp, from, to, this) } - - /** Substitute every occurrence of symbol `from_i` with `RefinedThis(leve).select(to_i)` - * where level represents the nesting level of the occurrence (i.e. number - * of refinedInfos between substituted type and the occurrence. - * TODO: Drop `rt` once it is dropped from RefinedThis - * TODO: Apply to enclosing refined type instead of refined info. That way we - * can optimize for types not containing any RefinedThis - */ - final class SubstWithRefinedSelectMap(rt: RefinedType, from: List[Symbol], to: List[TypeName]) extends DeepTypeMap { - private var level = 0 - def apply(tp: Type): Type = tp match { - case tp: NamedType => - val sym = tp.symbol - var fs = from - var ts = to - while (fs.nonEmpty) { - if (fs.head eq sym) - return RefinedThis(rt, level).select(ts.head) - fs = fs.tail - ts = ts.tail - } - if (sym.isStatic && !existsStatic(from)) tp - else tp.derivedSelect(apply(tp.prefix)) - case tp: RefinedType => - val parent1 = apply(tp.parent) - level += 1 - try tp.derivedRefinedType(parent1, tp.refinedName, apply(tp.refinedInfo)) - finally level -= 1 - case tp: TypeAlias => - tp.derivedTypeAlias(apply(tp.alias)) - case _ => - mapOver(tp) - } - } } \ No newline at end of file diff --git a/src/dotty/tools/dotc/core/TypeApplications.scala b/src/dotty/tools/dotc/core/TypeApplications.scala index e71226b68714..b29022281740 100644 --- a/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/src/dotty/tools/dotc/core/TypeApplications.scala @@ -373,29 +373,27 @@ class TypeApplications(val self: Type) extends AnyVal { case _ => firstBaseArgInfo(defn.SeqClass) } - def containsRefinedThis(level: Int)(implicit ctx: Context): Boolean = { - def recur(tp: Type, level: Int): Boolean = tp.stripTypeVar match { - case RefinedThis(rt, `level`) => - true + def containsRefinedThis(target: Type)(implicit ctx: Context): Boolean = { + def recur(tp: Type): Boolean = tp.stripTypeVar match { + case RefinedThis(tp) => + tp eq target case tp: NamedType => tp.info match { - case TypeAlias(alias) => recur(alias, level) - case _ => !tp.symbol.isStatic && recur(tp.prefix, level) + case TypeAlias(alias) => recur(alias) + case _ => !tp.symbol.isStatic && recur(tp.prefix) } case tp: RefinedType => - recur(tp.refinedInfo, level + 1) || - recur(tp.parent, level) + recur(tp.refinedInfo) || recur(tp.parent) case tp: TypeBounds => - recur(tp.lo, level) || - recur(tp.hi, level) + recur(tp.lo) || recur(tp.hi) case tp: AnnotatedType => - recur(tp.underlying, level) + recur(tp.underlying) case tp: AndOrType => - recur(tp.tp1, level) || recur(tp.tp2, level) + recur(tp.tp1) || recur(tp.tp2) case _ => false } - recur(self, level) + recur(self) } /** Given a type alias @@ -432,14 +430,16 @@ class TypeApplications(val self: Type) extends AnyVal { if (bsyms.isEmpty) { val correspondingNames = correspondingParamName.values.toSet + def replacements(rt: RefinedType): List[Type] = + for (sym <- boundSyms) + yield TypeRef(RefinedThis(rt), correspondingParamName(sym)) + def rewrite(tp: Type): Type = tp match { case tp @ RefinedType(parent, name: TypeName) => if (correspondingNames contains name) rewrite(parent) else RefinedType( - rewrite(parent), - name, - new ctx.SubstWithRefinedSelectMap( - _, boundSyms, boundSyms map correspondingParamName)(tp.refinedInfo)) + rewrite(parent), name, + rt => tp.refinedInfo.subst(boundSyms, replacements(rt))) case tp => tp } @@ -474,8 +474,9 @@ class TypeApplications(val self: Type) extends AnyVal { def expand(tp: Type) = { val lambda = defn.lambdaTrait(boundSyms.map(_.variance)) val substitutedRHS = (rt: RefinedType) => { - val argNames = boundSyms.indices.toList.map(tpnme.lambdaArgName) - new ctx.SubstWithRefinedSelectMap(rt, boundSyms, argNames)(tp).bounds.withVariance(1) + val argRefs = boundSyms.indices.toList.map(i => + RefinedThis(rt).select(tpnme.lambdaArgName(i))) + tp.subst(boundSyms, argRefs).bounds.withVariance(1) } val res = RefinedType(lambda.typeRef, tpnme.Apply, substitutedRHS) //println(i"lambda abstract $self wrt $boundSyms%, % --> $res") diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index 8717a45df244..8a810741a879 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -258,9 +258,9 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi } } comparePolyParam - case tp1: RefinedThis => + case RefinedThis(rt1) => tp2 match { - case tp2: RefinedThis if tp1.level == tp2.level => true + case RefinedThis(rt2) if rt1 == rt2 => true case _ => thirdTry(tp1, tp2) } case tp1: BoundType => @@ -525,7 +525,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi try { def rebindNeeded = tp2.refinementRefersToThis val base = if (rebindNeeded) ensureSingleton(tp1) else tp1 - val rinfo2 = if (rebindNeeded) tp2.refinedInfo.substRefinedThis(0, base) else tp2.refinedInfo + val rinfo2 = if (rebindNeeded) tp2.refinedInfo.substThis0(tp2, base) else tp2.refinedInfo def qualifies(m: SingleDenotation) = isSubType(m.info, rinfo2) def memberMatches(mbr: Denotation): Boolean = mbr match { // inlined hasAltWith for performance case mbr: SingleDenotation => qualifies(mbr) diff --git a/src/dotty/tools/dotc/core/TypeOps.scala b/src/dotty/tools/dotc/core/TypeOps.scala index 6fc7314c204a..3e04eb037537 100644 --- a/src/dotty/tools/dotc/core/TypeOps.scala +++ b/src/dotty/tools/dotc/core/TypeOps.scala @@ -331,21 +331,6 @@ trait TypeOps { this: Context => parentRefs } - /** Map `C.this` types where `C` is `refineCls` to RefinedThis types with given level. - * The level gets adjusted for nested refined types. - */ - def thisToRefinedThis(rt: RefinedType, refineCls: Symbol, level: Int): TypeMap = new TypeMap { - def apply(tp: Type): Type = tp match { - case tp: ThisType if tp.cls eq refineCls => RefinedThis(rt, level) - case tp: RefinedType => - tp.derivedRefinedType( - this(tp.parent), tp.refinedName, - thisToRefinedThis(rt, refineCls, level + 1)(tp.refinedInfo)) - case _ => - mapOver(tp) - } - } - /** An argument bounds violation is a triple consisting of * - the argument tree * - a string "upper" or "lower" indicating which bound is violated diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index d64e7340e426..933f56cb46e9 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -436,12 +436,9 @@ object Types { } def goRefined(tp: RefinedType) = { val pdenot = go(tp.parent) - val rinfo = pre match { - case pre: RefinedType => tp.refinedInfo.substThis0(tp, RefinedThis(pre, -1)) - case _ => - if (tp.refinementRefersToThis) tp.refinedInfo.substRefinedThis(0, pre) - else tp.refinedInfo - } + val rinfo = + if (tp.refinementRefersToThis) tp.refinedInfo.substThis0(tp, pre) + else tp.refinedInfo if (name.isTypeName) { // simplified case that runs more efficiently val jointInfo = if (rinfo.isAlias) rinfo else pdenot.info & rinfo pdenot.asSingleDenotation.derivedSingleDenotation(pdenot.symbol, jointInfo) @@ -777,12 +774,12 @@ object Types { case TypeAlias(tp) => if (!pre.refinementRefersToThis) tp else tp match { - case TypeRef(RefinedThis(_, 0), alias) => lookupRefined(alias) + case TypeRef(RefinedThis(`pre`), alias) => lookupRefined(alias) case _ => NoType } case _ => loop(pre.parent) } - case RefinedThis(rt, _) => + case RefinedThis(rt) => rt.lookupRefined(name) case pre: WildcardType => WildcardType @@ -947,10 +944,6 @@ object Types { final def substThis0(rt: RefinedType, tp: Type)(implicit ctx: Context): Type = ctx.substThis0(this, rt, tp, null) - /** Substitute all occurrences of `RefinedThis(level)` by `tp` */ - final def substRefinedThis(level: Int, to: Type)(implicit ctx: Context): Type = - ctx.substRefinedThis(this, level, to, null) - /** Substitute a bound type by some other type */ final def substParam(from: ParamType, to: Type)(implicit ctx: Context): Type = ctx.substParam(this, from, to, null) @@ -1750,49 +1743,15 @@ object Types { def refinementRefersToThis(implicit ctx: Context): Boolean = { if (!refinementRefersToThisKnown) { - refinementRefersToThisCache = refinedInfo.containsRefinedThis(0) + refinementRefersToThisCache = refinedInfo.containsRefinedThis(this) refinementRefersToThisKnown = true } refinementRefersToThisCache } - - def checkLevel(implicit ctx: Context): Unit = { - val checkAccu = new TypeAccumulator[Unit] { - var level = 0 - def apply(x: Unit, tp: Type) = tp.stripTypeVar match { - case RefinedThis(rt, l) => - def dominates(tp: Type, rt: RefinedType): Boolean = - (tp eq rt) || { - tp match { - case RefinedType(parent, _) => dominates(parent, rt) - case _ => false - } - } - if (rt eq RefinedType.this) assert(l == level, RefinedType.this) - case tp: RefinedType => - level += 1 - apply(x, tp.refinedInfo) - level -= 1 - apply(x, tp.parent) - case tp: TypeBounds => - apply(x, tp.lo) - apply(x, tp.hi) - case tp: AnnotatedType => - apply(x, tp.underlying) - case tp: AndOrType => - apply(x, tp.tp1) - apply(x, tp.tp2) - case _ => - foldOver(x, tp) - } - } - checkAccu((), refinedInfo) - } override def underlying(implicit ctx: Context) = parent private def checkInst(implicit ctx: Context): this.type = { - checkLevel if (Config.checkLambdaVariance) refinedInfo match { case refinedInfo: TypeBounds if refinedInfo.variance != 0 && refinedName.isLambdaArgName => @@ -1820,7 +1779,7 @@ object Types { derivedRefinedType(parent.EtaExpand, refinedName, refinedInfo) else if (false) RefinedType(parent, refinedName, refinedInfo) - else RefinedType(parent, refinedName, rt => refinedInfo.substThis0(this, RefinedThis(rt, -1))) // !!! TODO: replace with simply `refinedInfo` + else RefinedType(parent, refinedName, rt => refinedInfo.substThis0(this, RefinedThis(rt))) } override def equals(that: Any) = that match { @@ -2222,11 +2181,11 @@ object Types { * @param level The number of enclosing refined types between * the `this` reference and its target. */ - case class RefinedThis(binder: RefinedType, level: Int) extends BoundType with SingletonType { + case class RefinedThis(binder: RefinedType) extends BoundType with SingletonType { type BT = RefinedType override def underlying(implicit ctx: Context) = binder - def copyBoundType(bt: BT) = RefinedThis(bt, level) - + def copyBoundType(bt: BT) = RefinedThis(bt) + // need to customize hashCode and equals to prevent infinite recursion for // refinements that refer to the refinement type via this override def computeHash = addDelta(binder.identityHash, 41) @@ -2234,7 +2193,7 @@ object Types { case that: RefinedThis => this.binder eq that.binder case _ => false } - override def toString = s"RefinedThis($level, ${binder.hashCode})" + override def toString = s"RefinedThis(${binder.hashCode})" } // ------------ Type variables ---------------------------------------- diff --git a/src/dotty/tools/dotc/core/pickling/UnPickler.scala b/src/dotty/tools/dotc/core/pickling/UnPickler.scala index 2bea977f7b5e..62ad39c07a55 100644 --- a/src/dotty/tools/dotc/core/pickling/UnPickler.scala +++ b/src/dotty/tools/dotc/core/pickling/UnPickler.scala @@ -660,8 +660,12 @@ class UnPickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClassRoot: val parent = parents.reduceLeft(AndType(_, _)) if (decls.isEmpty) parent else { - def addRefinement(tp: Type, sym: Symbol) = - RefinedType(tp, sym.name, ctx.thisToRefinedThis(_, clazz, 0)(sym.info)) + def addRefinement(tp: Type, sym: Symbol) = { + def subst(info: Type, rt: RefinedType) = + if (clazz.isClass) info.substThis(clazz.asClass, RefinedThis(rt)) + else info // turns out some symbols read into `clazz` are not classes, not sure why this is the case. + RefinedType(tp, sym.name, subst(sym.info, _)) + } (parent /: decls.toList)(addRefinement).asInstanceOf[RefinedType] } case CLASSINFOtpe => diff --git a/src/dotty/tools/dotc/printing/PlainPrinter.scala b/src/dotty/tools/dotc/printing/PlainPrinter.scala index fd6ba44a7fec..77a308bba020 100644 --- a/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -207,8 +207,11 @@ class PlainPrinter(_ctx: Context) extends Printer { toText(value) case MethodParam(mt, idx) => nameString(mt.paramNames(idx)) - case RefinedThis(_, level) => - s"this<$level>" + case sk: RefinedThis => + sk.binder match { + case rt: RefinedType => s"${nameString(rt.typeSymbol)}{...}.this" + case _ => "" + } } } diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index dd05fea3f6e8..0ff47b36d024 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -791,7 +791,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit checkRefinementNonCyclic(refinement, refineCls, seen) val rsym = refinement.symbol val rinfo = if (rsym is Accessor) rsym.info.resultType else rsym.info - RefinedType(parent, rsym.name, ctx.thisToRefinedThis(_, refineCls, 0)(rinfo)) + RefinedType(parent, rsym.name, rt => rinfo.substThis(refineCls, RefinedThis(rt))) // todo later: check that refinement is within bounds } val res = cpy.RefinedTypeTree(tree)(tpt1, refinements1) withType diff --git a/tests/pos/Patterns.scala b/tests/pos/Patterns.scala index 98af1cddbcb1..54c4d8ab27b9 100644 --- a/tests/pos/Patterns.scala +++ b/tests/pos/Patterns.scala @@ -6,7 +6,7 @@ object Patterns { private def rebase(tp: NamedType): Type = { def rebaseFrom(prefix: Type): Type = ??? tp.prefix match { - case RefinedThis(rt, _) => rebaseFrom(rt) + case RefinedThis(rt) => rebaseFrom(rt) case pre: ThisType => rebaseFrom(pre) case _ => tp } From 1d8c014a0a7872120df2b46e1a7e305c98f5b4c5 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 10 Jan 2015 18:17:49 +0100 Subject: [PATCH 32/72] Rename RefinedThis -> SkolemType Also, make binder type of SkolemType refer to arbitrary type, not necessarily RefinedType. --- src/dotty/tools/dotc/core/Substituters.scala | 22 +++++----- .../tools/dotc/core/TypeApplications.scala | 8 ++-- src/dotty/tools/dotc/core/TypeComparer.scala | 6 +-- src/dotty/tools/dotc/core/Types.scala | 43 ++++++++----------- .../tools/dotc/core/pickling/UnPickler.scala | 2 +- .../tools/dotc/printing/PlainPrinter.scala | 2 +- src/dotty/tools/dotc/typer/Typer.scala | 2 +- tests/pos/Patterns.scala | 2 +- 8 files changed, 40 insertions(+), 47 deletions(-) diff --git a/src/dotty/tools/dotc/core/Substituters.scala b/src/dotty/tools/dotc/core/Substituters.scala index 76264519f7cb..b45522bf94c1 100644 --- a/src/dotty/tools/dotc/core/Substituters.scala +++ b/src/dotty/tools/dotc/core/Substituters.scala @@ -179,23 +179,21 @@ trait Substituters { this: Context => .mapOver(tp) } - // !!! TODO remove once we are sure new RefinedThis scheme works and we drop the ref to - // the target type from RefinedThis. - final def substThis0(tp: Type, from: RefinedType, to: Type, theMap: SubstRefinedThisMap0): Type = + final def substSkolem(tp: Type, from: Type, to: Type, theMap: SubstSkolemMap): Type = tp match { - case tp @ RefinedThis(rt) => - if (rt eq from) to else tp + case tp @ SkolemType(binder) => + if (binder eq from) to else tp case tp: NamedType => if (tp.currentSymbol.isStatic) tp - else tp.derivedSelect(substThis0(tp.prefix, from, to, theMap)) + else tp.derivedSelect(substSkolem(tp.prefix, from, to, theMap)) case _: ThisType | _: BoundType | NoPrefix => tp case tp: RefinedType => - tp.derivedRefinedType(substThis0(tp.parent, from, to, theMap), tp.refinedName, substThis0(tp.refinedInfo, from, to, theMap)) + tp.derivedRefinedType(substSkolem(tp.parent, from, to, theMap), tp.refinedName, substSkolem(tp.refinedInfo, from, to, theMap)) case tp: TypeAlias => - tp.derivedTypeAlias(substThis0(tp.alias, from, to, theMap)) + tp.derivedTypeAlias(substSkolem(tp.alias, from, to, theMap)) case _ => - (if (theMap != null) theMap else new SubstRefinedThisMap0(from, to)) + (if (theMap != null) theMap else new SubstSkolemMap(from, to)) .mapOver(tp) } @@ -224,7 +222,7 @@ trait Substituters { this: Context => case tp: NamedType => if (tp.currentSymbol.isStatic) tp else tp.derivedSelect(substParams(tp.prefix, from, to, theMap)) - case _: ThisType | NoPrefix | _: RefinedThis => + case _: ThisType | NoPrefix | _: SkolemType => tp case tp: RefinedType => tp.derivedRefinedType(substParams(tp.parent, from, to, theMap), tp.refinedName, substParams(tp.refinedInfo, from, to, theMap)) @@ -268,8 +266,8 @@ trait Substituters { this: Context => def apply(tp: Type): Type = substThis(tp, from, to, this) } - final class SubstRefinedThisMap0(from: RefinedType, to: Type) extends DeepTypeMap { - def apply(tp: Type): Type = substThis0(tp, from, to, this) + final class SubstSkolemMap(from: Type, to: Type) extends DeepTypeMap { + def apply(tp: Type): Type = substSkolem(tp, from, to, this) } final class SubstParamMap(from: ParamType, to: Type) extends DeepTypeMap { diff --git a/src/dotty/tools/dotc/core/TypeApplications.scala b/src/dotty/tools/dotc/core/TypeApplications.scala index b29022281740..e59c6095945b 100644 --- a/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/src/dotty/tools/dotc/core/TypeApplications.scala @@ -373,9 +373,9 @@ class TypeApplications(val self: Type) extends AnyVal { case _ => firstBaseArgInfo(defn.SeqClass) } - def containsRefinedThis(target: Type)(implicit ctx: Context): Boolean = { + def containsSkolemType(target: Type)(implicit ctx: Context): Boolean = { def recur(tp: Type): Boolean = tp.stripTypeVar match { - case RefinedThis(tp) => + case SkolemType(tp) => tp eq target case tp: NamedType => tp.info match { @@ -432,7 +432,7 @@ class TypeApplications(val self: Type) extends AnyVal { def replacements(rt: RefinedType): List[Type] = for (sym <- boundSyms) - yield TypeRef(RefinedThis(rt), correspondingParamName(sym)) + yield TypeRef(SkolemType(rt), correspondingParamName(sym)) def rewrite(tp: Type): Type = tp match { case tp @ RefinedType(parent, name: TypeName) => @@ -475,7 +475,7 @@ class TypeApplications(val self: Type) extends AnyVal { val lambda = defn.lambdaTrait(boundSyms.map(_.variance)) val substitutedRHS = (rt: RefinedType) => { val argRefs = boundSyms.indices.toList.map(i => - RefinedThis(rt).select(tpnme.lambdaArgName(i))) + SkolemType(rt).select(tpnme.lambdaArgName(i))) tp.subst(boundSyms, argRefs).bounds.withVariance(1) } val res = RefinedType(lambda.typeRef, tpnme.Apply, substitutedRHS) diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index 8a810741a879..ef0ac627f850 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -258,9 +258,9 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi } } comparePolyParam - case RefinedThis(rt1) => + case SkolemType(binder1) => tp2 match { - case RefinedThis(rt2) if rt1 == rt2 => true + case SkolemType(binder2) if binder1 == binder2 => true case _ => thirdTry(tp1, tp2) } case tp1: BoundType => @@ -525,7 +525,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi try { def rebindNeeded = tp2.refinementRefersToThis val base = if (rebindNeeded) ensureSingleton(tp1) else tp1 - val rinfo2 = if (rebindNeeded) tp2.refinedInfo.substThis0(tp2, base) else tp2.refinedInfo + val rinfo2 = if (rebindNeeded) tp2.refinedInfo.substSkolem(tp2, base) else tp2.refinedInfo def qualifies(m: SingleDenotation) = isSubType(m.info, rinfo2) def memberMatches(mbr: Denotation): Boolean = mbr match { // inlined hasAltWith for performance case mbr: SingleDenotation => qualifies(mbr) diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 933f56cb46e9..c476a8be9f74 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -49,7 +49,7 @@ object Types { * | | +--- SuperType * | | +--- ConstantType * | | +--- MethodParam - * | | +--- RefinedThis + * | | +--- SkolemType * | +- PolyParam * | +- RefinedType * | +- TypeBounds @@ -437,7 +437,7 @@ object Types { def goRefined(tp: RefinedType) = { val pdenot = go(tp.parent) val rinfo = - if (tp.refinementRefersToThis) tp.refinedInfo.substThis0(tp, pre) + if (tp.refinementRefersToThis) tp.refinedInfo.substSkolem(tp, pre) else tp.refinedInfo if (name.isTypeName) { // simplified case that runs more efficiently val jointInfo = if (rinfo.isAlias) rinfo else pdenot.info & rinfo @@ -774,13 +774,13 @@ object Types { case TypeAlias(tp) => if (!pre.refinementRefersToThis) tp else tp match { - case TypeRef(RefinedThis(`pre`), alias) => lookupRefined(alias) + case TypeRef(SkolemType(`pre`), alias) => lookupRefined(alias) case _ => NoType } case _ => loop(pre.parent) } - case RefinedThis(rt) => - rt.lookupRefined(name) + case SkolemType(binder) => + binder.lookupRefined(name) case pre: WildcardType => WildcardType case _ => @@ -940,9 +940,9 @@ object Types { final def substThisUnlessStatic(cls: ClassSymbol, tp: Type)(implicit ctx: Context): Type = if (cls.isStaticOwner) this else ctx.substThis(this, cls, tp, null) - /** Substitute all occurrences of `RefinedThis(rt)` by `tp` !!! TODO remove */ - final def substThis0(rt: RefinedType, tp: Type)(implicit ctx: Context): Type = - ctx.substThis0(this, rt, tp, null) + /** Substitute all occurrences of `SkolemType(binder)` by `tp` */ + final def substSkolem(binder: Type, tp: Type)(implicit ctx: Context): Type = + ctx.substSkolem(this, binder, tp, null) /** Substitute a bound type by some other type */ final def substParam(from: ParamType, to: Type)(implicit ctx: Context): Type = @@ -1334,7 +1334,7 @@ object Types { * to an (unbounded) wildcard type. * * (2) Reduce a type-ref `T { X = U; ... } # X` to `U` - * provided `U` does not refer with a RefinedThis to the + * provided `U` does not refer with a SkolemType to the * refinement type `T { X = U; ... }` */ def reduceProjection(implicit ctx: Context): Type = { @@ -1743,7 +1743,7 @@ object Types { def refinementRefersToThis(implicit ctx: Context): Boolean = { if (!refinementRefersToThisKnown) { - refinementRefersToThisCache = refinedInfo.containsRefinedThis(this) + refinementRefersToThisCache = refinedInfo.containsSkolemType(this) refinementRefersToThisKnown = true } refinementRefersToThisCache @@ -1779,7 +1779,7 @@ object Types { derivedRefinedType(parent.EtaExpand, refinedName, refinedInfo) else if (false) RefinedType(parent, refinedName, refinedInfo) - else RefinedType(parent, refinedName, rt => refinedInfo.substThis0(this, RefinedThis(rt))) + else RefinedType(parent, refinedName, rt => refinedInfo.substSkolem(this, SkolemType(rt))) } override def equals(that: Any) = that match { @@ -2109,10 +2109,10 @@ object Types { } } - // ----- Bound types: MethodParam, PolyParam, RefinedThis -------------------------- + // ----- Bound types: MethodParam, PolyParam, SkolemType -------------------------- abstract class BoundType extends CachedProxyType with ValueType { - type BT <: BindingType + type BT <: Type def binder: BT // Dotty deviation: copyBoundType was copy, but // dotty generates copy methods always automatically, and therefore @@ -2175,25 +2175,20 @@ object Types { override def computeHash = doHash(paramNum, binder) } - /** A reference to the `this` of an enclosing refined type. - * @param binder The refinement type referred to the RefinedThis when - * it was created. - * @param level The number of enclosing refined types between - * the `this` reference and its target. - */ - case class RefinedThis(binder: RefinedType) extends BoundType with SingletonType { - type BT = RefinedType + /** A skolem type reference with underlying type `binder`. */ + case class SkolemType(binder: Type) extends BoundType with SingletonType { + type BT = Type override def underlying(implicit ctx: Context) = binder - def copyBoundType(bt: BT) = RefinedThis(bt) + def copyBoundType(bt: BT) = SkolemType(bt) // need to customize hashCode and equals to prevent infinite recursion for // refinements that refer to the refinement type via this override def computeHash = addDelta(binder.identityHash, 41) override def equals(that: Any) = that match { - case that: RefinedThis => this.binder eq that.binder + case that: SkolemType => this.binder eq that.binder case _ => false } - override def toString = s"RefinedThis(${binder.hashCode})" + override def toString = s"SkolemType(${binder.hashCode})" } // ------------ Type variables ---------------------------------------- diff --git a/src/dotty/tools/dotc/core/pickling/UnPickler.scala b/src/dotty/tools/dotc/core/pickling/UnPickler.scala index 62ad39c07a55..894ad8f803fc 100644 --- a/src/dotty/tools/dotc/core/pickling/UnPickler.scala +++ b/src/dotty/tools/dotc/core/pickling/UnPickler.scala @@ -662,7 +662,7 @@ class UnPickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClassRoot: else { def addRefinement(tp: Type, sym: Symbol) = { def subst(info: Type, rt: RefinedType) = - if (clazz.isClass) info.substThis(clazz.asClass, RefinedThis(rt)) + if (clazz.isClass) info.substThis(clazz.asClass, SkolemType(rt)) else info // turns out some symbols read into `clazz` are not classes, not sure why this is the case. RefinedType(tp, sym.name, subst(sym.info, _)) } diff --git a/src/dotty/tools/dotc/printing/PlainPrinter.scala b/src/dotty/tools/dotc/printing/PlainPrinter.scala index 77a308bba020..e86539d6cfde 100644 --- a/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -207,7 +207,7 @@ class PlainPrinter(_ctx: Context) extends Printer { toText(value) case MethodParam(mt, idx) => nameString(mt.paramNames(idx)) - case sk: RefinedThis => + case sk: SkolemType => sk.binder match { case rt: RefinedType => s"${nameString(rt.typeSymbol)}{...}.this" case _ => "" diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index 0ff47b36d024..9958b3b64ca2 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -791,7 +791,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit checkRefinementNonCyclic(refinement, refineCls, seen) val rsym = refinement.symbol val rinfo = if (rsym is Accessor) rsym.info.resultType else rsym.info - RefinedType(parent, rsym.name, rt => rinfo.substThis(refineCls, RefinedThis(rt))) + RefinedType(parent, rsym.name, rt => rinfo.substThis(refineCls, SkolemType(rt))) // todo later: check that refinement is within bounds } val res = cpy.RefinedTypeTree(tree)(tpt1, refinements1) withType diff --git a/tests/pos/Patterns.scala b/tests/pos/Patterns.scala index 54c4d8ab27b9..e443c2ab5c3f 100644 --- a/tests/pos/Patterns.scala +++ b/tests/pos/Patterns.scala @@ -6,7 +6,7 @@ object Patterns { private def rebase(tp: NamedType): Type = { def rebaseFrom(prefix: Type): Type = ??? tp.prefix match { - case RefinedThis(rt) => rebaseFrom(rt) + case SkolemType(rt) => rebaseFrom(rt) case pre: ThisType => rebaseFrom(pre) case _ => tp } From 90c8ac30fd0898dfc07b389f742c8baa2f5642e9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 10 Jan 2015 18:20:23 +0100 Subject: [PATCH 33/72] Simplified condition in isSubType. --- src/dotty/tools/dotc/core/TypeComparer.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index ef0ac627f850..4eaea31e1a4f 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -258,9 +258,9 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi } } comparePolyParam - case SkolemType(binder1) => + case tp1: SkolemType => tp2 match { - case SkolemType(binder2) if binder1 == binder2 => true + case tp2: SkolemType if tp1 == tp2 => true case _ => thirdTry(tp1, tp2) } case tp1: BoundType => From 9c53aaa7e3381eb684ff7eb71ab8b980cfa51abc Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 11 Jan 2015 12:30:52 +0100 Subject: [PATCH 34/72] Adapted Skolemization to new handling of skolems. --- src/dotty/tools/dotc/core/Skolemization.scala | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/src/dotty/tools/dotc/core/Skolemization.scala b/src/dotty/tools/dotc/core/Skolemization.scala index 320150c4458d..d9e35af9dead 100644 --- a/src/dotty/tools/dotc/core/Skolemization.scala +++ b/src/dotty/tools/dotc/core/Skolemization.scala @@ -4,6 +4,14 @@ package core import Symbols._, Types._, Contexts._ import collection.mutable +/** Methods to add and remove skolemtypes. + * + * Skolem types are generated when comparing refinements. + * Two skolem types are equal if they refer to the same underlying type. + * To avoid unsoundness, skolem types have to be kept strictly local to the + * comparison, they are not allowed to escape the lifetime of a comparison + * by surviving in a context or in GADT bounds. + */ trait Skolemization { implicit val ctx: Context @@ -15,30 +23,34 @@ trait Skolemization { tp case tp: ValueType => skolemsOutstanding = true - tp.narrow + SkolemType(tp) case tp: TypeProxy => ensureSingleton(tp.underlying) } - /** Approximate a type `tp` with a type that does not contain skolem termrefs. + /** Approximate a type `tp` with a type that does not contain skolem types. * @param toSuper if true, return the smallest supertype of `tp` with this property - * e;se return the largest subtype. + * else return the largest subtype. */ final def deSkolemize(tp: Type, toSuper: Boolean): Type = - if (skolemsOutstanding) deSkolemize(tp, if (toSuper) 1 else -1, Set()) else tp + if (skolemsOutstanding) { + skolemsOutstanding = false + deSkolemize(tp, if (toSuper) 1 else -1, Set()) + } + else tp - private def deSkolemize(tp: Type, variance: Int, seen: Set[Symbol]): Type = + private def deSkolemize(tp: Type, variance: Int, seen: Set[SkolemType]): Type = ctx.traceIndented(s"deskolemize $tp, variance = $variance, seen = $seen = ") { - def approx(lo: Type = defn.NothingType, hi: Type = defn.AnyType, newSeen: Set[Symbol] = seen) = + def approx(lo: Type = defn.NothingType, hi: Type = defn.AnyType, newSeen: Set[SkolemType] = seen) = if (variance == 0) NoType else deSkolemize(if (variance < 0) lo else hi, variance, newSeen) tp match { + case tp: SkolemType => + if (seen contains tp) NoType + else approx(hi = tp.binder, newSeen = seen + tp) case tp: NamedType => val sym = tp.symbol - if (sym.isSkolem) - if (seen contains sym) NoType - else approx(hi = sym.info, newSeen = seen + sym) - else if (sym.isStatic) tp + if (sym.isStatic) tp else { val pre1 = deSkolemize(tp.prefix, variance, seen) if (pre1.exists && !pre1.isRef(defn.NothingClass)) tp.derivedSelect(pre1) @@ -98,9 +110,9 @@ trait Skolemization { } object deSkolemizeMap extends TypeMap { - private var seen: Set[Symbol] = _ + private var seen: Set[SkolemType] = _ def apply(tp: Type) = deSkolemize(tp, variance, seen) - def mapOver(tp: Type, variance: Int, seen: Set[Symbol]) = { + def mapOver(tp: Type, variance: Int, seen: Set[SkolemType]) = { val savedVariance = this.variance val savedSeen = this.seen this.variance = variance From 7df0423e49e81904ba703d44b0389d3a544aa946 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 12 Jan 2015 12:36:45 +0100 Subject: [PATCH 35/72] Made constraint data structures pluggable. Factored out interface for constraints. Current implementation: NaiveConstraint. Preparing for a more efficient one. --- src/dotty/tools/dotc/core/Constraint.scala | 536 ++++-------------- .../tools/dotc/core/ConstraintHandling.scala | 18 +- .../tools/dotc/core/ConstraintRunInfo.scala | 16 + .../tools/dotc/core/NaiveConstraint.scala | 383 +++++++++++++ src/dotty/tools/dotc/core/TypeComparer.scala | 7 - src/dotty/tools/dotc/core/TyperState.scala | 2 +- src/dotty/tools/dotc/core/Types.scala | 2 +- tests/pos/subtyping.scala | 13 + 8 files changed, 521 insertions(+), 456 deletions(-) create mode 100644 src/dotty/tools/dotc/core/ConstraintRunInfo.scala create mode 100644 src/dotty/tools/dotc/core/NaiveConstraint.scala diff --git a/src/dotty/tools/dotc/core/Constraint.scala b/src/dotty/tools/dotc/core/Constraint.scala index 86d686d3731a..a5c55ccce559 100644 --- a/src/dotty/tools/dotc/core/Constraint.scala +++ b/src/dotty/tools/dotc/core/Constraint.scala @@ -10,489 +10,143 @@ import printing.Texts._ import config.Config import config.Printers._ -object Constraint { - - /** The type of `Constraint#myMap` */ - type ParamInfo = SimpleMap[PolyType, Array[Type]] - - /** The type of `Constraint#dependents */ - type DependentMap = SimpleMap[PolyType, Array[Set[PolyParam]]] - - /** The type of functions that include or exclude a `PolyParam` in or from a set*/ - private type DepDelta = (Set[PolyParam], PolyParam) => Set[PolyParam] - - private val addDep: DepDelta = (_ + _) - private val removeDep: DepDelta = (_ - _) - - private val NoTypeBounds = new TypeBounds(WildcardType, WildcardType) { - override def computeHash = unsupported("computeHash") - } - - /** An accumulator that changes dependencies on `param`. - * @param param The parameter to which changed dependencies refer. - * @param ofVariance Include `PolyParams` occurring at this variance in the dependencies. - * @param delta The dependency change to perform (add or remove). - */ - private class ChangeDependencies(param: PolyParam, ofVariance: Int, delta: DepDelta)(implicit ctx: Context) - extends TypeAccumulator[DependentMap] { - def apply(deps: DependentMap, tp: Type): DependentMap = tp match { - case tp @ PolyParam(pt, n) if - this.variance == 0 || this.variance == ofVariance => - val oldDeps = deps(pt) - val original = safeSelect(oldDeps, n) - val changed = delta(original, param) - if (original eq changed) deps - else { - val newDeps = - if (oldDeps == null) new Array[Set[PolyParam]](pt.paramBounds.length) - else oldDeps.clone - newDeps(n) = changed - deps.updated(pt, newDeps) - } - case _ => foldOver(deps, tp) - } - } - - /** `deps(n)`, except that `Set()` is returned if `deps` or `deps(n)` are null */ - private def safeSelect(deps: Array[Set[PolyParam]], n: Int) : Set[PolyParam] = - if (deps == null || deps(n) == null) Set() - else deps(n) -} - -import Constraint._ - -/** Constraint over undetermined type parameters - * @param myMap a map from PolyType to arrays. - * Each array contains twice the number of entries as there a type parameters - * in the PolyType. The first half of the array contains the type bounds that constrain the - * polytype's type parameters. The second half might contain type variables that - * track the corresponding parameters, or is left empty (filled with nulls). - * An instantiated type parameter is represented by having its instance type in - * the corresponding array entry. - * @param dependents a map from PolyTypes to arrays of Sets of PolyParams. - * The i'th set in an array corresponding to polytype `pt` contains - * those dependent `PolyParam`s `dp` that have `PolyParam(pt, i)` in their bounds in - * significant position. A position is significant if solving the - * constraint for `(pt, i)` with a type higher than its lower bound - * would lead to a constraint for `dp` that was not looser than - * the existing constraint. Specifically, it means that all poly params - * appearing covariantly in the lower bound and contravariantly in the - * upper bound, as well as all poly params appearing nonvariantly are - * significant. - * The `dependents` map is maintained and queried only of `Config.trackConstrDeps` is set. +/** Constraint over undetermined type parameters. Constraints are built + * over values of the following types: + * + * - PolyType A constraint constrains the type parameters of a set of PolyTypes + * - PolyParam The parameters of the constrained polytypes + * - TypeVar Every constrained parameter might be associated with a TypeVar + * that has the PolyParam as origin. */ -class Constraint(private val myMap: ParamInfo, - private val dependents: DependentMap) extends Showable { - +abstract class Constraint extends Showable { + + type This <: Constraint + /** Does the constraint's domain contain the type parameters of `pt`? */ - def contains(pt: PolyType): Boolean = myMap(pt) != null + def contains(pt: PolyType): Boolean /** Does the constraint's domain contain the type parameter `param`? */ - def contains(param: PolyParam): Boolean = { - val entries = myMap(param.binder) - entries != null && entries(param.paramNum).isInstanceOf[TypeBounds] - } + def contains(param: PolyParam): Boolean /** Does this constraint contain the type variable `tvar` and is it uninstantiated? */ - def contains(tvar: TypeVar): Boolean = { - val origin = tvar.origin - val entries = myMap(origin.binder) - val pnum = origin.paramNum - entries != null && isBounds(entries(pnum)) && (typeVar(entries, pnum) eq tvar) - } - - /** The number of type parameters in the given entry array */ - private def paramCount(entries: Array[Type]) = entries.length >> 1 - - /** The type variable corresponding to parameter numbered `n`, null if none was created */ - private def typeVar(entries: Array[Type], n: Int): Type = - entries(paramCount(entries) + n) - - private def isBounds(tp: Type) = tp.isInstanceOf[TypeBounds] - + def contains(tvar: TypeVar): Boolean + /** The constraint for given type parameter `param`, or NoType if `param` is not part of * the constraint domain. */ - def at(param: PolyParam): Type = { - val entries = myMap(param.binder) - if (entries == null) NoType else entries(param.paramNum) - } - - /** The constraint bounds for given type parameter `param`. - * @pre `param` is not part of the constraint domain. - */ - def bounds(param: PolyParam): TypeBounds = at(param).asInstanceOf[TypeBounds] - + def at(param: PolyParam): Type + /** The type variable corresponding to parameter `param`, or * NoType, if `param` is not in constrained or is not paired with a type variable. */ - def typeVarOfParam(param: PolyParam): Type = { - val entries = myMap(param.binder) - if (entries == null) NoType - else { - val tvar = typeVar(entries, param.paramNum) - if (tvar != null) tvar else NoType - } - } - - /** Change dependencies in map `deps` to reflect new parameter bounds. - * @param deps The map to change - * @param pt the polytype that contains the parameters which might have new bounds - * @param entries the entries for the parameters which might have new bounds - * @param delta the change operation, one of `addDep` or `removeDep`. - * @param cmpEntries the comparison entries or `null` if no such entries exist. - * As an optimization, only bounds that differ between `entries` - * and `cmpEntries` will record their dependencies. - */ - def changeDependencies(deps: DependentMap, pt: PolyType, entries: Array[Type], delta: DepDelta, cmpEntries: Array[Type])(implicit ctx: Context): DependentMap = { - val limit = paramCount(entries) - def loop(deps: DependentMap, n: Int): DependentMap = { - if (n >= limit) deps - else { - val newDeps = entries(n) match { - case bounds @ TypeBounds(lo, hi) => - val cmpBounds = - if (cmpEntries == null) NoTypeBounds - else cmpEntries(n) match { - case bounds: TypeBounds => bounds - case _ => NoTypeBounds - } - if (cmpBounds eq bounds) deps - else { - val param = PolyParam(pt, n) - val deps1 = - if (cmpBounds.lo eq lo) deps - else new ChangeDependencies(param, 1, delta).apply(deps, lo) - val deps2 = - if (cmpBounds.hi eq hi) deps1 - else new ChangeDependencies(param, -1, delta).apply(deps1, hi) - deps2 - } - case _ => - deps - } - loop(newDeps, n + 1) - } - } - if (Config.trackConstrDeps) loop(deps, 0) else deps - } - - /** Change dependencies to reflect all changes between the bounds in `oldMap` and `newMap`. - */ - def diffDependencies(deps: DependentMap, oldMap: ParamInfo, newMap: ParamInfo)(implicit ctx: Context): DependentMap = - if (Config.trackConstrDeps) { - var d = deps - oldMap foreachBinding { (poly, entries) => - val newEntries = newMap(poly) - if (newEntries ne entries) d = changeDependencies(d, poly, entries, removeDep, newEntries) - } - newMap foreachBinding { (poly, entries) => - val oldEntries = oldMap(poly) - if (oldEntries ne entries) d = changeDependencies(d, poly, entries, addDep, oldEntries) - } - d - } else deps - - /** The set of parameters that depend directly on `param` - * according to what's stored in `dependents`. - */ - def dependentParams(param: PolyParam): Set[PolyParam] = - safeSelect(dependents(param.binder), param.paramNum) - - /** A new constraint which is derived from this constraint by adding or replacing - * the entries corresponding to `pt` with `entries`. + def typeVarOfParam(param: PolyParam): Type + + /** The constraint bounds for given type parameter `param`. + * @pre `param` is not part of the constraint domain. */ - private def updateEntries(pt: PolyType, entries: Array[Type])(implicit ctx: Context) : Constraint = { - val res = new Constraint( - myMap.updated(pt, entries), - changeDependencies(dependents, pt, entries, addDep, myMap(pt))) - - //assert(res.domainPolys.filter(pt => - // pt.resultType.resultType.widen.classSymbol.name.toString == "Ensuring").length < 2) //DEBUG - if (Config.checkConstraintsNonCyclic) checkNonCyclic(pt, entries) - ctx.runInfo.recordConstraintSize(res, res.myMap.size) - res - } - - /** Check that no constrained parameter in `pt` contains itself as a bound */ - def checkNonCyclic(pt: PolyType, entries: Array[Type])(implicit ctx: Context): Unit = - for ((entry, i) <- entries.zipWithIndex) { - val param = PolyParam(pt, i) - entry match { - case TypeBounds(lo, hi) => - assert(!param.occursIn(lo, fromBelow = true), s"$param occurs below $lo") - assert(!param.occursIn(hi, fromBelow = false), s"$param occurs above $hi") - case _ => - } - } - - /** Check that no constrained parameter contains itself as a bound */ - def checkNonCyclic()(implicit ctx: Context): Unit = { - for (pt <- domainPolys) checkNonCyclic(pt, myMap(pt)) - } - - /** Check that no constrained parameter contains itself as a bound, - * either directly or indirectly. This should be not a structer criterion - * than checkNonCyclic because transitivity should be eliminated always, - * but it's good to be paranoid. + def bounds(param: PolyParam): TypeBounds + + /** The non-parameter bounds of this constraint. + * Poly params that are `related` are not contained in the return bounds. */ - def checkNonCyclicTrans()(implicit ctx: Context): Unit = { - for (pt <- domainPolys) - checkNonCyclicTrans(pt, myMap(pt)) - } + def nonParamBounds(param: PolyParam)(implicit ctx: Context): TypeBounds - def checkNonCyclicTrans(pt: PolyType, entries: Array[Type])(implicit ctx: Context): Unit = - for ((entry, i) <- entries.zipWithIndex) { - def occursIn(params: Set[PolyParam], bound: Type, fromBelow: Boolean): Boolean = bound.stripTypeVar match { - case bound: PolyParam => - params.contains(bound) || { - at(bound) match { - case TypeBounds(lo, hi) => - occursIn(params + bound, if (fromBelow) lo else hi, fromBelow) - case _ => - false - } - } - case bound: AndOrType => - def occ1 = occursIn(params, bound.tp1, fromBelow) - def occ2 = occursIn(params, bound.tp2, fromBelow) - if (fromBelow == bound.isAnd) occ1 && occ2 else occ1 || occ2 - case _ => false - } - val param = PolyParam(pt, i) - entry match { - case TypeBounds(lo, hi) => - assert(!occursIn(Set(param), lo, fromBelow = true), s"$param occurs below $lo") - assert(!occursIn(Set(param), hi, fromBelow = false), s"$param occurs above $hi") - case _ => - } - } + /** If `firstIsLower` is `param1 <:< param2`? + * Otherwise, is `param2 <:< param1`? + */ + def related(param1: PolyParam, param2: PolyParam, firstIsLower: Boolean)(implicit ctx: Context): Boolean + /** A new constraint which is derived from this constraint by adding + * entries for all type parameters of `poly`. + * @param tvars A list of type variables associated with the params, + * or Nil if the constraint will just be checked for + * satisfiability but will solved to give instances of + * type variables. + */ + def add(poly: PolyType, tvars: List[TypeVar])(implicit ctx: Context): This /** A new constraint which is derived from this constraint by updating * the entry for parameter `param` to `tpe`. * @pre `this contains param`. + * + * `tp` can be one of the following: + * + * - A TypeBounds value, indicating new constraint bounds + * - Another type, indicating a solution for the parameter */ - def updated(param: PolyParam, tpe: Type)(implicit ctx: Context): Constraint = { - val newEntries = myMap(param.binder).clone - newEntries(param.paramNum) = tpe - updateEntries(param.binder, newEntries) - } - - /** A new constraint which is derived from this constraint by mapping - * `op` over all entries of `poly`. - * @pre `this contains poly`. - */ - def transformed(poly: PolyType, op: Type => Type)(implicit ctx: Context) : Constraint = - updateEntries(poly, myMap(poly) map op) - - /** A new constraint with all entries coming from `pt` removed. */ - def remove(pt: PolyType)(implicit ctx: Context) = - new Constraint( - myMap remove pt, - changeDependencies(dependents, pt, myMap(pt), removeDep, null)) - - /** Is entry associated with `pt` removable? - * @param removedParam The index of a parameter which is still present in the - * entry array, but is going to be removed at the same step, - * or -1 if no such parameter exists. - */ - def isRemovable(pt: PolyType, removedParam: Int = -1): Boolean = { - val entries = myMap(pt) - var noneLeft = true - var i = paramCount(entries) - while (noneLeft && i > 0) { - i -= 1 - if (i != removedParam && isBounds(entries(i))) noneLeft = false - else typeVar(entries, i) match { - case tv: TypeVar => - if (!tv.inst.exists) noneLeft = false // need to keep line around to compute instType - case _ => - } - } - noneLeft - } - - /** Drop parameter `PolyParam(poly, n)` from `bounds`, - * replacing with Nothing in the lower bound and by `Any` in the upper bound. + def updated(param: PolyParam, tp: Type)(implicit ctx: Context): This + + /** A constraint that includes a the relationship `bound <: param` if `fromBelow` is true + * or else `param <: bound` if `fromBelow` is false. */ - def dropParamIn(bounds: TypeBounds, poly: PolyType, n: Int)(implicit ctx: Context): TypeBounds = { - def drop(tp: Type): Type = tp match { - case tp: AndOrType => - val tp1 = drop(tp.tp1) - val tp2 = drop(tp.tp2) - if (!tp1.exists) tp2 - else if (!tp2.exists) tp1 - else tp - case PolyParam(`poly`, `n`) => NoType - case _ => tp - } - def approx(tp: Type, limit: Type): Type = { - val tp1 = drop(tp) - if (tp1.exists || !tp.exists) tp1 else limit - } - bounds.derivedTypeBounds( - approx(bounds.lo, defn.NothingType), approx(bounds.hi, defn.AnyType)) - } + def order(param: PolyParam, bound: PolyParam, fromBelow: Boolean)(implicit ctx: Context): This /** A new constraint which is derived from this constraint by removing * the type parameter `param` from the domain and replacing all occurrences * of the parameter elsewhere in the constraint by type `tp`. - */ - private def uncheckedReplace(param: PolyParam, tp: Type)(implicit ctx: Context): Constraint = { - - def subst(poly: PolyType, entries: Array[Type]) = { - var result = entries - var i = 0 - while (i < paramCount(entries)) { - entries(i) match { - case oldBounds: TypeBounds => - val newBounds = oldBounds.substParam(param, tp).asInstanceOf[TypeBounds] - if (oldBounds ne newBounds) { - if (result eq entries) result = entries.clone - result(i) = dropParamIn(newBounds, poly, i) - } - case _ => - } - i += 1 - } - result - } - - val pt = param.binder - val constr1 = if (isRemovable(pt, param.paramNum)) remove(pt) else updated(param, tp) - val substMap = constr1.myMap mapValues subst - val result = new Constraint( - substMap, - diffDependencies(constr1.dependents, constr1.myMap, substMap)) - if (Config.checkConstraintsNonCyclic) result.checkNonCyclic() - result - } - - /** A new constraint which is derived from this constraint by removing - * the type parameter `param` from the domain and replacing all occurrences - * of the parameter elsewhere in the constraint by type `tp`. - * `tp` is another polyparam, applies the necessary unifications to avoud a cyclic + * If `tp` is another polyparam, applies the necessary unifications to avoid a cyclic * constraint. */ - def replace(param: PolyParam, tp: Type)(implicit ctx: Context): Constraint = - tp.dealias.stripTypeVar match { - case tp: PolyParam if this contains tp => - val bs = bounds(tp) - if (tp == param) - this - else if (param.occursIn(bs.lo, fromBelow = true) || - param.occursIn(bs.hi, fromBelow = false)) - unify(tp, param).uncheckedReplace(param, tp) - else - uncheckedReplace(param, tp) - case _ => - uncheckedReplace(param, tp) - } + def replace(param: PolyParam, tp: Type)(implicit ctx: Context): This + + /** Is entry associated with `pt` removable? + * @param removedParam The index of a parameter which is still present in the + * entry array, but is going to be removed at the same step, + * or -1 if no such parameter exists. + */ + def isRemovable(pt: PolyType, removedParam: Int = -1): Boolean + + /** A new constraint with all entries coming from `pt` removed. */ + def remove(pt: PolyType)(implicit ctx: Context): This /** A constraint resulting by adding p2 = p1 to this constraint, and at the same * time transferring all bounds of p2 to p1 */ - def unify(p1: PolyParam, p2: PolyParam)(implicit ctx: Context): Constraint = { - val p1Bounds = - dropParamIn(bounds(p1), p2.binder, p2.paramNum) & - dropParamIn(bounds(p2), p1.binder, p1.paramNum) - this.updated(p1, p1Bounds).updated(p2, TypeAlias(p1)) - } - - /** A new constraint which is derived from this constraint by adding - * entries for all type parameters of `poly`. - */ - def add(poly: PolyType, tvars: List[TypeVar] = Nil)(implicit ctx: Context) : Constraint = { - val nparams = poly.paramNames.length - val entries = new Array[Type](nparams * 2) - poly.paramBounds.copyToArray(entries, 0) - tvars.copyToArray(entries, nparams) - updateEntries(poly, entries) - } + def unify(p1: PolyParam, p2: PolyParam)(implicit ctx: Context): This /** The polytypes constrained by this constraint */ - def domainPolys: List[PolyType] = myMap.keys + def domainPolys: List[PolyType] /** The polytype parameters constrained by this constraint */ - def domainParams: List[PolyParam] = - for { - (poly, entries) <- myMap.toList - n <- 0 until paramCount(entries) - if isBounds(entries(n)) - } yield PolyParam(poly, n) + def domainParams: List[PolyParam] - /** Check whether predicate holds for all parameters in constraint - */ - def forallParams(p: PolyParam => Boolean): Boolean = { - myMap.foreachBinding { (poly, entries) => - for (i <- 0 until paramCount(entries)) - if (isBounds(entries(i)) && !p(PolyParam(poly, i))) return false - } - true - } + /** Check whether predicate holds for all parameters in constraint */ + def forallParams(p: PolyParam => Boolean): Boolean /** Perform operation `op` on all typevars, or only on uninstantiated * typevars, depending on whether `uninstOnly` is set or not. */ - def foreachTypeVar(op: TypeVar => Unit): Unit = - myMap.foreachBinding { (poly, entries) => - for (i <- 0 until paramCount(entries)) { - typeVar(entries, i) match { - case tv: TypeVar if !tv.inst.exists => op(tv) - case _ => - } - } - } - - private var myUninstVars: mutable.ArrayBuffer[TypeVar] = null + def foreachTypeVar(op: TypeVar => Unit): Unit /** The uninstantiated typevars of this constraint */ - def uninstVars: collection.Seq[TypeVar] = { - if (myUninstVars == null) { - myUninstVars = new mutable.ArrayBuffer[TypeVar] - myMap.foreachBinding { (poly, entries) => - for (i <- 0 until paramCount(entries)) { - typeVar(entries, i) match { - case tv: TypeVar if isBounds(entries(i)) => myUninstVars += tv - case _ => - } - } - } - } - myUninstVars - } + def uninstVars: collection.Seq[TypeVar] - def constrainedTypesText(printer: Printer): Text = - Text(domainPolys map (_.toText(printer)), ", ") - - def constraintText(indent: Int, printer: Printer): Text = { - val assocs = - for (param <- domainParams) - yield (" " * indent) ~ param.toText(printer) ~ at(param).toText(printer) - Text(assocs, "\n") - } - - override def toText(printer: Printer): Text = { - val header: Text = "Constraint(" - val uninstVarsText = " uninstVars = " ~ - Text(uninstVars map (_.toText(printer)), ", ") ~ ";" - val constrainedText = - " constrained types = " ~ constrainedTypesText(printer) ~ ";" - val constraintsText = - " constraint = " ~ constraintText(3, printer) ~ ")" - Text.lines(List(header, uninstVarsText, constrainedText, constraintsText)) - } -} - -trait ConstraintRunInfo { self: RunInfo => - private var maxSize = 0 - private var maxConstraint: Constraint = _ - def recordConstraintSize(c: Constraint, size: Int) = - if (size > maxSize) { - maxSize = size - maxConstraint = c - } - def printMaxConstraint()(implicit ctx: Context) = - if (maxSize > 0) typr.println(s"max constraint = ${maxConstraint.show}") + /** Check that no constrained parameter in `pt` contains itself as a bound */ + def checkNonCyclic(pt: PolyType, entries: Array[Type])(implicit ctx: Context): Unit + + /** Check that no constrained parameter contains itself as a bound */ + def checkNonCyclic()(implicit ctx: Context): Unit + + /** Check that no constrained parameter contains itself as a bound, + * either directly or indirectly. This should be not a stricter criterion + * than checkNonCyclic because transitivity should be eliminated always, + * but it's good to be paranoid. + */ + def checkNonCyclicTrans()(implicit ctx: Context): Unit + + protected def splitParams(tp: Type, seenFromBelow: Boolean, handleParam: PolyParam => Unit)(implicit ctx: Context): Type = tp match { + case tp: PolyParam if contains(tp) => + handleParam(tp) + NoType + case tp: AndOrType if seenFromBelow == tp.isAnd => + val tp1 = splitParams(tp.tp1, seenFromBelow, handleParam) + val tp2 = splitParams(tp.tp2, seenFromBelow, handleParam) + if (tp1.exists) + if (tp2.exists) tp.derivedAndOrType(tp1, tp2) + else tp1 + else if (tp2.exists) tp2 + else NoType + case _ => + tp + } } diff --git a/src/dotty/tools/dotc/core/ConstraintHandling.scala b/src/dotty/tools/dotc/core/ConstraintHandling.scala index 98649234f4de..d8078e26b90a 100644 --- a/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -62,8 +62,15 @@ trait ConstraintHandling { } } + protected def isSubTypeWhenFrozen(tp1: Type, tp2: Type): Boolean = { + val saved = frozenConstraint + frozenConstraint = true + try isSubType(tp1, tp2) + finally frozenConstraint = saved + } + /** The current bounds of type parameter `param` */ - def bounds(param: PolyParam): TypeBounds = constraint at param match { + final def bounds(param: PolyParam): TypeBounds = constraint at param match { case bounds: TypeBounds if !ignoreConstraint => bounds case _ => param.binder.paramBounds(param.paramNum) } @@ -204,7 +211,6 @@ trait ConstraintHandling { } if (ok) newBounds else NoType } - val bound = deSkolemize(bound0, toSuper = fromBelow).dealias.stripTypeVar def description = s"${param.show} ${if (fromBelow) ">:>" else "<:<"} ${bound.show} to ${constraint.show}" constr.println(s"adding $description") @@ -214,13 +220,13 @@ trait ConstraintHandling { res } - def isConstrained(param: PolyParam): Boolean = + final def isConstrained(param: PolyParam): Boolean = !frozenConstraint && !solvedConstraint && (constraint contains param) /** Test whether the lower bounds of all parameters in this * constraint are a solution to the constraint. */ - def isSatisfiable: Boolean = { + final def isSatisfiable: Boolean = { val saved = solvedConstraint solvedConstraint = true try @@ -244,7 +250,7 @@ trait ConstraintHandling { * @return the instantiating type * @pre `param` is in the constraint's domain. */ - def approximation(param: PolyParam, fromBelow: Boolean): Type = { + final def approximation(param: PolyParam, fromBelow: Boolean): Type = { val avoidParam = new TypeMap { override def stopAtStatic = true def apply(tp: Type) = mapOver { @@ -264,7 +270,7 @@ trait ConstraintHandling { /** Map that approximates each param in constraint by its lower bound. * Currently only used for diagnostics. */ - def approxParams = new TypeMap { // !!! Dotty problem: Turn this def into a val => -Ycheck:mix fails + final def approxParams = new TypeMap { // !!! Dotty problem: Turn this def into a val => -Ycheck:mix fails def apply(tp: Type): Type = tp.stripTypeVar match { case tp: PolyParam if constraint contains tp => this(constraint.bounds(tp).lo) diff --git a/src/dotty/tools/dotc/core/ConstraintRunInfo.scala b/src/dotty/tools/dotc/core/ConstraintRunInfo.scala new file mode 100644 index 000000000000..4b7e22653e0b --- /dev/null +++ b/src/dotty/tools/dotc/core/ConstraintRunInfo.scala @@ -0,0 +1,16 @@ +package dotty.tools.dotc +package core + +import Contexts._, config.Printers._ + +trait ConstraintRunInfo { self: RunInfo => + private var maxSize = 0 + private var maxConstraint: Constraint = _ + def recordConstraintSize(c: Constraint, size: Int) = + if (size > maxSize) { + maxSize = size + maxConstraint = c + } + def printMaxConstraint()(implicit ctx: Context) = + if (maxSize > 0) typr.println(s"max constraint = ${maxConstraint.show}") +} diff --git a/src/dotty/tools/dotc/core/NaiveConstraint.scala b/src/dotty/tools/dotc/core/NaiveConstraint.scala new file mode 100644 index 000000000000..34affc35a819 --- /dev/null +++ b/src/dotty/tools/dotc/core/NaiveConstraint.scala @@ -0,0 +1,383 @@ +package dotty.tools +package dotc +package core + +import Types._, Contexts._, Symbols._ +import util.SimpleMap +import collection.mutable +import printing.{Printer, Showable} +import printing.Texts._ +import config.Config +import config.Printers._ + +object NaiveConstraint { + + /** The type of `Constraint#myMap` */ + type ParamInfo = SimpleMap[PolyType, Array[Type]] + + /** The type of `Constraint#dependents */ + type DependentMap = SimpleMap[PolyType, Array[Set[PolyParam]]] + + /** The type of functions that include or exclude a `PolyParam` in or from a set*/ + private type DepDelta = (Set[PolyParam], PolyParam) => Set[PolyParam] + + private val addDep: DepDelta = (_ + _) + private val removeDep: DepDelta = (_ - _) + + private val NoTypeBounds = new TypeBounds(WildcardType, WildcardType) { + override def computeHash = unsupported("computeHash") + } + + /** An accumulator that changes dependencies on `param`. + * @param param The parameter to which changed dependencies refer. + * @param ofVariance Include `PolyParams` occurring at this variance in the dependencies. + * @param delta The dependency change to perform (add or remove). + */ + private class ChangeDependencies(param: PolyParam, ofVariance: Int, delta: DepDelta)(implicit ctx: Context) + extends TypeAccumulator[DependentMap] { + def apply(deps: DependentMap, tp: Type): DependentMap = tp match { + case tp @ PolyParam(pt, n) if + this.variance == 0 || this.variance == ofVariance => + val oldDeps = deps(pt) + val original = safeSelect(oldDeps, n) + val changed = delta(original, param) + if (original eq changed) deps + else { + val newDeps = + if (oldDeps == null) new Array[Set[PolyParam]](pt.paramBounds.length) + else oldDeps.clone + newDeps(n) = changed + deps.updated(pt, newDeps) + } + case _ => foldOver(deps, tp) + } + } + + /** `deps(n)`, except that `Set()` is returned if `deps` or `deps(n)` are null */ + private def safeSelect(deps: Array[Set[PolyParam]], n: Int) : Set[PolyParam] = + if (deps == null || deps(n) == null) Set() + else deps(n) + + private def ignoreParam(p: PolyParam): Unit = {} +} + +import NaiveConstraint._ + +/** Constraint over undetermined type parameters + * @param myMap a map from PolyType to arrays. + * Each array contains twice the number of entries as there a type parameters + * in the PolyType. The first half of the array contains the type bounds that constrain the + * polytype's type parameters. The second half might contain type variables that + * track the corresponding parameters, or is left empty (filled with nulls). + * An instantiated type parameter is represented by having its instance type in + * the corresponding array entry. + */ +class NaiveConstraint(private val myMap: ParamInfo) extends Constraint { + + type This = NaiveConstraint + + def contains(pt: PolyType): Boolean = myMap(pt) != null + + def contains(param: PolyParam): Boolean = { + val entries = myMap(param.binder) + entries != null && entries(param.paramNum).isInstanceOf[TypeBounds] + } + + def contains(tvar: TypeVar): Boolean = { + val origin = tvar.origin + val entries = myMap(origin.binder) + val pnum = origin.paramNum + entries != null && isBounds(entries(pnum)) && (typeVar(entries, pnum) eq tvar) + } + + /** The number of type parameters in the given entry array */ + private def paramCount(entries: Array[Type]) = entries.length >> 1 + + /** The type variable corresponding to parameter numbered `n`, null if none was created */ + private def typeVar(entries: Array[Type], n: Int): Type = + entries(paramCount(entries) + n) + + private def isBounds(tp: Type) = tp.isInstanceOf[TypeBounds] + + def at(param: PolyParam): Type = { + val entries = myMap(param.binder) + if (entries == null) NoType else entries(param.paramNum) + } + + def bounds(param: PolyParam): TypeBounds = at(param).asInstanceOf[TypeBounds] + + def nonParamBounds(param: PolyParam)(implicit ctx: Context): TypeBounds = { + val bs @ TypeBounds(lo, hi) = bounds(param) + val lo1 = splitParams(lo, seenFromBelow = false, ignoreParam) + val hi1 = splitParams(hi, seenFromBelow = true, ignoreParam) + bs.derivedTypeBounds(lo1.orElse(defn.NothingType), hi1.orElse(defn.AnyType)) + } + + def related(param1: PolyParam, param2: PolyParam, firstIsLower: Boolean)(implicit ctx: Context): Boolean = { + var isRelated = false + def registerParam(p: PolyParam) = if (p == param2) isRelated = true + val TypeBounds(lo, hi) = bounds(param1) + if (firstIsLower) splitParams(hi, seenFromBelow = true, registerParam) + else splitParams(lo, seenFromBelow = false, registerParam) + isRelated + } + + def typeVarOfParam(param: PolyParam): Type = { + val entries = myMap(param.binder) + if (entries == null) NoType + else { + val tvar = typeVar(entries, param.paramNum) + if (tvar != null) tvar else NoType + } + } + + /** A new constraint which is derived from this constraint by adding or replacing + * the entries corresponding to `pt` with `entries`. + */ + private def updateEntries(pt: PolyType, entries: Array[Type])(implicit ctx: Context) : NaiveConstraint = { + val res = new NaiveConstraint(myMap.updated(pt, entries)) + + //assert(res.domainPolys.filter(pt => + // pt.resultType.resultType.widen.classSymbol.name.toString == "Ensuring").length < 2) //DEBUG + if (Config.checkConstraintsNonCyclic) checkNonCyclic(pt, entries) + ctx.runInfo.recordConstraintSize(res, res.myMap.size) + res + } + + def checkNonCyclic(pt: PolyType, entries: Array[Type])(implicit ctx: Context): Unit = + for ((entry, i) <- entries.zipWithIndex) { + val param = PolyParam(pt, i) + entry match { + case TypeBounds(lo, hi) => + assert(!param.occursIn(lo, fromBelow = true), s"$param occurs below $lo") + assert(!param.occursIn(hi, fromBelow = false), s"$param occurs above $hi") + case _ => + } + } + + def checkNonCyclic()(implicit ctx: Context): Unit = { + for (pt <- domainPolys) checkNonCyclic(pt, myMap(pt)) + } + + /** Check that no constrained parameter contains itself as a bound, + * either directly or indirectly. This should be not a structer criterion + * than checkNonCyclic because transitivity should be eliminated always, + * but it's good to be paranoid. + */ + def checkNonCyclicTrans()(implicit ctx: Context): Unit = { + for (pt <- domainPolys) + checkNonCyclicTrans(pt, myMap(pt)) + } + + private def checkNonCyclicTrans(pt: PolyType, entries: Array[Type])(implicit ctx: Context): Unit = + for ((entry, i) <- entries.zipWithIndex) { + def occursIn(params: Set[PolyParam], bound: Type, fromBelow: Boolean): Boolean = bound.stripTypeVar match { + case bound: PolyParam => + params.contains(bound) || { + at(bound) match { + case TypeBounds(lo, hi) => + occursIn(params + bound, if (fromBelow) lo else hi, fromBelow) + case _ => + false + } + } + case bound: AndOrType => + def occ1 = occursIn(params, bound.tp1, fromBelow) + def occ2 = occursIn(params, bound.tp2, fromBelow) + if (fromBelow == bound.isAnd) occ1 && occ2 else occ1 || occ2 + case _ => false + } + val param = PolyParam(pt, i) + entry match { + case TypeBounds(lo, hi) => + assert(!occursIn(Set(param), lo, fromBelow = true), s"$param occurs below $lo") + assert(!occursIn(Set(param), hi, fromBelow = false), s"$param occurs above $hi") + case _ => + } + } + + def updated(param: PolyParam, tpe: Type)(implicit ctx: Context): This = { + val newEntries = myMap(param.binder).clone + newEntries(param.paramNum) = tpe + updateEntries(param.binder, newEntries) + } + + def order(param: PolyParam, bound: PolyParam, fromBelow: Boolean)(implicit ctx: Context): This = + if (related(param, bound, firstIsLower = !fromBelow)) this + else { + val oldBounds = bounds(param) + val newBounds = + if (fromBelow) TypeBounds(OrType(oldBounds.lo, bound), oldBounds.hi) + else TypeBounds(oldBounds.lo, AndType(oldBounds.hi, bound)) + updated(param, newBounds) + } + + /** A new constraint with all entries coming from `pt` removed. */ + def remove(pt: PolyType)(implicit ctx: Context): This = + new NaiveConstraint(myMap remove pt) + + def isRemovable(pt: PolyType, removedParam: Int = -1): Boolean = { + val entries = myMap(pt) + var noneLeft = true + var i = paramCount(entries) + while (noneLeft && i > 0) { + i -= 1 + if (i != removedParam && isBounds(entries(i))) noneLeft = false + else typeVar(entries, i) match { + case tv: TypeVar => + if (!tv.inst.exists) noneLeft = false // need to keep line around to compute instType + case _ => + } + } + noneLeft + } + + /** Drop parameter `PolyParam(poly, n)` from `bounds`, + * replacing with Nothing in the lower bound and by `Any` in the upper bound. + */ + private def dropParamIn(bounds: TypeBounds, poly: PolyType, n: Int)(implicit ctx: Context): TypeBounds = { + def drop(tp: Type): Type = tp match { + case tp: AndOrType => + val tp1 = drop(tp.tp1) + val tp2 = drop(tp.tp2) + if (!tp1.exists) tp2 + else if (!tp2.exists) tp1 + else tp + case PolyParam(`poly`, `n`) => NoType + case _ => tp + } + def approx(tp: Type, limit: Type): Type = { + val tp1 = drop(tp) + if (tp1.exists || !tp.exists) tp1 else limit + } + bounds.derivedTypeBounds( + approx(bounds.lo, defn.NothingType), approx(bounds.hi, defn.AnyType)) + } + + /** A new constraint which is derived from this constraint by removing + * the type parameter `param` from the domain and replacing all occurrences + * of the parameter elsewhere in the constraint by type `tp`. + */ + private def uncheckedReplace(param: PolyParam, tp: Type)(implicit ctx: Context): NaiveConstraint = { + + def subst(poly: PolyType, entries: Array[Type]) = { + var result = entries + var i = 0 + while (i < paramCount(entries)) { + entries(i) match { + case oldBounds: TypeBounds => + val newBounds = oldBounds.substParam(param, tp).asInstanceOf[TypeBounds] + if (oldBounds ne newBounds) { + if (result eq entries) result = entries.clone + result(i) = dropParamIn(newBounds, poly, i) + } + case _ => + } + i += 1 + } + result + } + + val pt = param.binder + val constr1 = if (isRemovable(pt, param.paramNum)) remove(pt) else updated(param, tp) + val result = new NaiveConstraint(constr1.myMap mapValues subst) + if (Config.checkConstraintsNonCyclic) result.checkNonCyclic() + result + } + + def replace(param: PolyParam, tp: Type)(implicit ctx: Context): This = + tp.dealias.stripTypeVar match { + case tp: PolyParam if this contains tp => + val bs = bounds(tp) + if (tp == param) + this + else if (param.occursIn(bs.lo, fromBelow = true) || + param.occursIn(bs.hi, fromBelow = false)) + unify(tp, param).uncheckedReplace(param, tp) + else + uncheckedReplace(param, tp) + case _ => + uncheckedReplace(param, tp) + } + + def unify(p1: PolyParam, p2: PolyParam)(implicit ctx: Context): This = { + val p1Bounds = + dropParamIn(bounds(p1), p2.binder, p2.paramNum) & + dropParamIn(bounds(p2), p1.binder, p1.paramNum) + this.updated(p1, p1Bounds).updated(p2, TypeAlias(p1)) + } + + def add(poly: PolyType, tvars: List[TypeVar])(implicit ctx: Context): This = { + val nparams = poly.paramNames.length + val entries = new Array[Type](nparams * 2) + poly.paramBounds.copyToArray(entries, 0) + tvars.copyToArray(entries, nparams) + updateEntries(poly, entries) + } + + def domainPolys: List[PolyType] = myMap.keys + + def domainParams: List[PolyParam] = + for { + (poly, entries) <- myMap.toList + n <- 0 until paramCount(entries) + if isBounds(entries(n)) + } yield PolyParam(poly, n) + + def forallParams(p: PolyParam => Boolean): Boolean = { + myMap.foreachBinding { (poly, entries) => + for (i <- 0 until paramCount(entries)) + if (isBounds(entries(i)) && !p(PolyParam(poly, i))) return false + } + true + } + + def foreachTypeVar(op: TypeVar => Unit): Unit = + myMap.foreachBinding { (poly, entries) => + for (i <- 0 until paramCount(entries)) { + typeVar(entries, i) match { + case tv: TypeVar if !tv.inst.exists => op(tv) + case _ => + } + } + } + + private var myUninstVars: mutable.ArrayBuffer[TypeVar] = null + + /** The uninstantiated typevars of this constraint */ + def uninstVars: collection.Seq[TypeVar] = { + if (myUninstVars == null) { + myUninstVars = new mutable.ArrayBuffer[TypeVar] + myMap.foreachBinding { (poly, entries) => + for (i <- 0 until paramCount(entries)) { + typeVar(entries, i) match { + case tv: TypeVar if isBounds(entries(i)) => myUninstVars += tv + case _ => + } + } + } + } + myUninstVars + } + + def constrainedTypesText(printer: Printer): Text = + Text(domainPolys map (_.toText(printer)), ", ") + + def constraintText(indent: Int, printer: Printer): Text = { + val assocs = + for (param <- domainParams) + yield (" " * indent) ~ param.toText(printer) ~ at(param).toText(printer) + Text(assocs, "\n") + } + + override def toText(printer: Printer): Text = { + val header: Text = "Constraint(" + val uninstVarsText = " uninstVars = " ~ + Text(uninstVars map (_.toText(printer)), ", ") ~ ";" + val constrainedText = + " constrained types = " ~ constrainedTypesText(printer) ~ ";" + val constraintsText = + " constraint = " ~ constraintText(3, printer) ~ ")" + Text.lines(List(header, uninstVarsText, constrainedText, constraintsText)) + } +} diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index 4eaea31e1a4f..457b29829787 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -513,13 +513,6 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi false } else isSubType(tp1, tp2) - private def isSubTypeWhenFrozen(tp1: Type, tp2: Type): Boolean = { - val saved = frozenConstraint - frozenConstraint = true - try isSubType(tp1, tp2) - finally frozenConstraint = saved - } - protected def hasMatchingMember(name: Name, tp1: Type, tp2: RefinedType): Boolean = { val saved = skolemsOutstanding try { diff --git a/src/dotty/tools/dotc/core/TyperState.scala b/src/dotty/tools/dotc/core/TyperState.scala index 86dc0d24e1cd..5d910c905e82 100644 --- a/src/dotty/tools/dotc/core/TyperState.scala +++ b/src/dotty/tools/dotc/core/TyperState.scala @@ -17,7 +17,7 @@ class TyperState(r: Reporter) extends DotClass with Showable { def reporter = r /** The current constraint set */ - def constraint: Constraint = new Constraint(SimpleMap.Empty, SimpleMap.Empty) + def constraint: Constraint = new NaiveConstraint(SimpleMap.Empty) def constraint_=(c: Constraint): Unit = {} /** The uninstantiated variables */ diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index c476a8be9f74..080596321275 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -744,7 +744,7 @@ object Types { } /** A prefix-less refined this or a termRef to a new skolem symbol - * that has the given type as info + * that has the given type as info. */ def narrow(implicit ctx: Context): TermRef = TermRef(NoPrefix, ctx.newSkolem(this)) diff --git a/tests/pos/subtyping.scala b/tests/pos/subtyping.scala index 95e813bddece..29d830dd2c8b 100644 --- a/tests/pos/subtyping.scala +++ b/tests/pos/subtyping.scala @@ -16,4 +16,17 @@ object test { } +object test2 { + + class A + class B + + val x: A | B = ??? + val y: B | A = x + + val a: A & B = ??? + val b: B & A = a + +} + From f76e81ed5cc5f57fcbdde6ea98503bd031c903a2 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 12 Jan 2015 18:37:47 +0100 Subject: [PATCH 36/72] Streamline unification Instead of recording a `TypeAlias(p)` for the entry of an eliminated parameter we record directly the solution `p`. --- .../tools/dotc/core/ConstraintHandling.scala | 67 +++++++++---------- .../tools/dotc/core/NaiveConstraint.scala | 2 +- 2 files changed, 33 insertions(+), 36 deletions(-) diff --git a/src/dotty/tools/dotc/core/ConstraintHandling.scala b/src/dotty/tools/dotc/core/ConstraintHandling.scala index d8078e26b90a..264cff0127ac 100644 --- a/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -107,44 +107,41 @@ trait ConstraintHandling { * make sure 'B >: A' gets added and vice versa. Furthermore, if the constraint * implies 'A <: B <: A', A and B get unified. */ - def addc(param: PolyParam, bound: Type, fromBelow: Boolean): Boolean = - constraint.bounds(param) match { - case TypeBounds(plo: PolyParam, phi) if (plo eq phi) && constraint.contains(plo) => - addc(plo, bound, fromBelow) - case pbounds0 => - bound match { - case bound: PolyParam if constraint contains bound => - val bbounds0 @ TypeBounds(lo, hi) = constraint.bounds(bound) - if (lo eq hi) - addc(param, lo, fromBelow) - else if (param == bound) - true - else if (fromBelow && param.occursIn(lo, fromBelow = true)) - unify(param, bound) - else if (!fromBelow && param.occursIn(hi, fromBelow = false)) - unify(bound, param) - else { - val pbounds = prepare(param, bound, fromBelow) - val bbounds = prepare(bound, param, !fromBelow) - pbounds.exists && bbounds.exists && { - install(param, pbounds.bounds, pbounds0) - install(bound, bbounds.bounds, bbounds0) - true - } - } - case bound: AndOrType if fromBelow != bound.isAnd => - addc(param, bound.tp1, fromBelow) && - addc(param, bound.tp2, fromBelow) - case bound: WildcardType => + def addc(param: PolyParam, bound: Type, fromBelow: Boolean): Boolean = { + val pbounds0 = constraint.bounds(param) + bound match { + case bound: PolyParam if constraint contains bound => + val bbounds0 @ TypeBounds(lo, hi) = constraint.bounds(bound) + if (lo eq hi) + addc(param, lo, fromBelow) + else if (param == bound) + true + else if (fromBelow && param.occursIn(lo, fromBelow = true)) + unify(param, bound) + else if (!fromBelow && param.occursIn(hi, fromBelow = false)) + unify(bound, param) + else { + val pbounds = prepare(param, bound, fromBelow) + val bbounds = prepare(bound, param, !fromBelow) + pbounds.exists && bbounds.exists && { + install(param, pbounds.bounds, pbounds0) + install(bound, bbounds.bounds, bbounds0) true - case bound => // !!! remove to keep the originals - val pbounds = prepare(param, bound, fromBelow) - pbounds.exists && { - install(param, pbounds.bounds, pbounds0) - true - } + } + } + case bound: AndOrType if fromBelow != bound.isAnd => + addc(param, bound.tp1, fromBelow) && + addc(param, bound.tp2, fromBelow) + case bound: WildcardType => + true + case bound => // !!! remove to keep the originals + val pbounds = prepare(param, bound, fromBelow) + pbounds.exists && { + install(param, pbounds.bounds, pbounds0) + true } } + } /** Install bounds for param */ def install(param: PolyParam, newBounds: TypeBounds, oldBounds: TypeBounds): Unit = { diff --git a/src/dotty/tools/dotc/core/NaiveConstraint.scala b/src/dotty/tools/dotc/core/NaiveConstraint.scala index 34affc35a819..f79a7d05faa1 100644 --- a/src/dotty/tools/dotc/core/NaiveConstraint.scala +++ b/src/dotty/tools/dotc/core/NaiveConstraint.scala @@ -304,7 +304,7 @@ class NaiveConstraint(private val myMap: ParamInfo) extends Constraint { val p1Bounds = dropParamIn(bounds(p1), p2.binder, p2.paramNum) & dropParamIn(bounds(p2), p1.binder, p1.paramNum) - this.updated(p1, p1Bounds).updated(p2, TypeAlias(p1)) + this.updated(p1, p1Bounds).updated(p2, p1) } def add(poly: PolyType, tvars: List[TypeVar])(implicit ctx: Context): This = { From 0b02ede80cfbc9c585114bb0dd51436dbf15de5f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 13 Jan 2015 17:19:17 +0100 Subject: [PATCH 37/72] Added structural equals method to PolyParams --- src/dotty/tools/dotc/core/Types.scala | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 080596321275..0363a8c706b7 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -2173,6 +2173,12 @@ object Types { override def toString = s"PolyParam(${binder.paramNames(paramNum)})" override def computeHash = doHash(paramNum, binder) + override def equals(that: Any) = that match { + case that: PolyParam => + (this.binder eq that.binder) && this.paramNum == that.paramNum + case _ => + false + } } /** A skolem type reference with underlying type `binder`. */ From 155049286628843eb7aeb2d8d610ac53291ea6f3 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 13 Jan 2015 17:20:09 +0100 Subject: [PATCH 38/72] Added alternative TrackingConstraint This is an alternative low-level constraint implementation. --- src/dotty/tools/dotc/core/Constraint.scala | 10 +- .../tools/dotc/core/ConstraintHandling.scala | 8 +- .../tools/dotc/core/NaiveConstraint.scala | 41 +- .../tools/dotc/core/TrackingConstraint.scala | 517 ++++++++++++++++++ src/dotty/tools/dotc/core/TyperState.scala | 4 +- src/dotty/tools/dotc/typer/ProtoTypes.scala | 2 +- 6 files changed, 550 insertions(+), 32 deletions(-) create mode 100644 src/dotty/tools/dotc/core/TrackingConstraint.scala diff --git a/src/dotty/tools/dotc/core/Constraint.scala b/src/dotty/tools/dotc/core/Constraint.scala index a5c55ccce559..f7e7a2e13601 100644 --- a/src/dotty/tools/dotc/core/Constraint.scala +++ b/src/dotty/tools/dotc/core/Constraint.scala @@ -34,7 +34,7 @@ abstract class Constraint extends Showable { /** The constraint for given type parameter `param`, or NoType if `param` is not part of * the constraint domain. */ - def at(param: PolyParam): Type + def at(param: PolyParam)(implicit ctx: Context): Type /** The type variable corresponding to parameter `param`, or * NoType, if `param` is not in constrained or is not paired with a type variable. @@ -44,7 +44,7 @@ abstract class Constraint extends Showable { /** The constraint bounds for given type parameter `param`. * @pre `param` is not part of the constraint domain. */ - def bounds(param: PolyParam): TypeBounds + def bounds(param: PolyParam)(implicit ctx: Context): TypeBounds /** The non-parameter bounds of this constraint. * Poly params that are `related` are not contained in the return bounds. @@ -76,10 +76,10 @@ abstract class Constraint extends Showable { */ def updated(param: PolyParam, tp: Type)(implicit ctx: Context): This - /** A constraint that includes a the relationship `bound <: param` if `fromBelow` is true - * or else `param <: bound` if `fromBelow` is false. + /** A constraint that includes a the relationship `param <: bound` if `inOrder` is true + * or else `bound <: param` if `inOrdxer` is false. */ - def order(param: PolyParam, bound: PolyParam, fromBelow: Boolean)(implicit ctx: Context): This + def order(param: PolyParam, bound: PolyParam, inOrder: Boolean)(implicit ctx: Context): This /** A new constraint which is derived from this constraint by removing * the type parameter `param` from the domain and replacing all occurrences diff --git a/src/dotty/tools/dotc/core/ConstraintHandling.scala b/src/dotty/tools/dotc/core/ConstraintHandling.scala index 264cff0127ac..2aa2a4acd246 100644 --- a/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -146,7 +146,13 @@ trait ConstraintHandling { /** Install bounds for param */ def install(param: PolyParam, newBounds: TypeBounds, oldBounds: TypeBounds): Unit = { val curBounds = constraint.bounds(param) - constraint = constraint.updated(param, newBounds) + try { + constraint = constraint.updated(param, newBounds) + } catch { + case ex: AssertionError => + println(i"error while updating $param $newBounds\n$constraint") + throw ex + } if (curBounds ne oldBounds) { // In this case the bounds were updated previously by a recursive isSubType in // the satisfiability check of prepare. Reapply the previously added bounds, but diff --git a/src/dotty/tools/dotc/core/NaiveConstraint.scala b/src/dotty/tools/dotc/core/NaiveConstraint.scala index f79a7d05faa1..ae5d5b0f1593 100644 --- a/src/dotty/tools/dotc/core/NaiveConstraint.scala +++ b/src/dotty/tools/dotc/core/NaiveConstraint.scala @@ -99,12 +99,17 @@ class NaiveConstraint(private val myMap: ParamInfo) extends Constraint { private def isBounds(tp: Type) = tp.isInstanceOf[TypeBounds] - def at(param: PolyParam): Type = { + def at(param: PolyParam)(implicit ctx: Context): Type = { + val entries = myMap(param.binder) + if (entries == null) NoType else entries(param.paramNum) + } + + def entry(param: PolyParam) = { val entries = myMap(param.binder) if (entries == null) NoType else entries(param.paramNum) } - def bounds(param: PolyParam): TypeBounds = at(param).asInstanceOf[TypeBounds] + def bounds(param: PolyParam)(implicit ctx: Context): TypeBounds = at(param).asInstanceOf[TypeBounds] def nonParamBounds(param: PolyParam)(implicit ctx: Context): TypeBounds = { val bs @ TypeBounds(lo, hi) = bounds(param) @@ -113,11 +118,11 @@ class NaiveConstraint(private val myMap: ParamInfo) extends Constraint { bs.derivedTypeBounds(lo1.orElse(defn.NothingType), hi1.orElse(defn.AnyType)) } - def related(param1: PolyParam, param2: PolyParam, firstIsLower: Boolean)(implicit ctx: Context): Boolean = { + def related(param1: PolyParam, param2: PolyParam, inOrder: Boolean)(implicit ctx: Context): Boolean = { var isRelated = false def registerParam(p: PolyParam) = if (p == param2) isRelated = true val TypeBounds(lo, hi) = bounds(param1) - if (firstIsLower) splitParams(hi, seenFromBelow = true, registerParam) + if (inOrder) splitParams(hi, seenFromBelow = true, registerParam) else splitParams(lo, seenFromBelow = false, registerParam) isRelated } @@ -202,13 +207,13 @@ class NaiveConstraint(private val myMap: ParamInfo) extends Constraint { updateEntries(param.binder, newEntries) } - def order(param: PolyParam, bound: PolyParam, fromBelow: Boolean)(implicit ctx: Context): This = - if (related(param, bound, firstIsLower = !fromBelow)) this + def order(param: PolyParam, bound: PolyParam, inOrder: Boolean)(implicit ctx: Context): This = + if (related(param, bound, inOrder)) this else { val oldBounds = bounds(param) val newBounds = - if (fromBelow) TypeBounds(OrType(oldBounds.lo, bound), oldBounds.hi) - else TypeBounds(oldBounds.lo, AndType(oldBounds.hi, bound)) + if (inOrder) TypeBounds(oldBounds.lo, AndType(oldBounds.hi, bound)) + else TypeBounds(OrType(oldBounds.lo, bound), oldBounds.hi) updated(param, newBounds) } @@ -285,20 +290,10 @@ class NaiveConstraint(private val myMap: ParamInfo) extends Constraint { result } - def replace(param: PolyParam, tp: Type)(implicit ctx: Context): This = - tp.dealias.stripTypeVar match { - case tp: PolyParam if this contains tp => - val bs = bounds(tp) - if (tp == param) - this - else if (param.occursIn(bs.lo, fromBelow = true) || - param.occursIn(bs.hi, fromBelow = false)) - unify(tp, param).uncheckedReplace(param, tp) - else - uncheckedReplace(param, tp) - case _ => - uncheckedReplace(param, tp) - } + def replace(param: PolyParam, tp: Type)(implicit ctx: Context): This = { + val tp1 = tp.dealias.stripTypeVar + if (param == tp1) this else uncheckedReplace(param, tp1) + } def unify(p1: PolyParam, p2: PolyParam)(implicit ctx: Context): This = { val p1Bounds = @@ -366,7 +361,7 @@ class NaiveConstraint(private val myMap: ParamInfo) extends Constraint { def constraintText(indent: Int, printer: Printer): Text = { val assocs = for (param <- domainParams) - yield (" " * indent) ~ param.toText(printer) ~ at(param).toText(printer) + yield (" " * indent) ~ param.toText(printer) ~ entry(param).toText(printer) Text(assocs, "\n") } diff --git a/src/dotty/tools/dotc/core/TrackingConstraint.scala b/src/dotty/tools/dotc/core/TrackingConstraint.scala new file mode 100644 index 000000000000..6e9ef5a2ba8d --- /dev/null +++ b/src/dotty/tools/dotc/core/TrackingConstraint.scala @@ -0,0 +1,517 @@ +package dotty.tools +package dotc +package core + +import Types._, Contexts._, Symbols._, Decorators._ +import util.SimpleMap +import collection.mutable +import printing.{Printer, Showable} +import printing.Texts._ +import config.Config +import config.Printers._ +import collection.immutable.BitSet +import reflect.ClassTag + +object TrackingConstraint { + + /** The type of `Constraint#myMap` */ + type ParamInfo = SimpleMap[PolyType, Array[Type]] + +} + +import TrackingConstraint._ + +/** Constraint over undetermined type parameters + * @param myMap a map from PolyType to arrays. + * Each array contains twice the number of entries as there a type parameters + * in the PolyType. The first half of the array contains the type bounds that constrain the + * polytype's type parameters. The second half might contain type variables that + * track the corresponding parameters, or is left empty (filled with nulls). + * An instantiated type parameter is represented by having its instance type in + * the corresponding array entry. + */ +class TrackingConstraint(private val myMap: ParamInfo, + private val less: Array[BitSet], + private val params: Array[PolyParam]) extends Constraint { + + type This = TrackingConstraint + + assert(less.length == params.length) + + /** A new constraint which is derived from this constraint by adding or replacing + * the entries corresponding to `pt` with `entries`. + */ + private def newConstraint(myMap: ParamInfo, less: Array[BitSet], params: Array[PolyParam])(implicit ctx: Context) : TrackingConstraint = { + val result = new TrackingConstraint(myMap, less, params) + if (Config.checkConstraintsNonCyclic) result.checkNonCyclic() + ctx.runInfo.recordConstraintSize(result, result.myMap.size) + result + } + +// ----------- Basic indices -------------------------------------------------- + + /** The immutable array of constrained polytypes */ + private val polyTypes = new Array[PolyType](myMap.size) + + /** The start positions of parameters of constrained polytypes in `params` and `less` */ + private val polyStart = new Array[Int](myMap.size) + + { + var idx = 0 + var count = 0 + myMap.foreachBinding { (pt, _) => + polyTypes(idx) = pt + polyStart(idx) = count + count += pt.paramNames.length + idx += 1 + } + assert(count == params.length) + } + + /** The index of given polytype `pt` in this constraint, + * or `polyTypes.length` if constraint does not contain `pt`. + */ + private def polyIndex(pt: PolyType): Int = { + var i = 0 + while (i < polyTypes.length && (polyTypes(i) ne pt)) i += 1 + i + } + + /** The index of the first parameter of given polytype `pt` in this constraint */ + private def polyStart(pt: PolyType): Int = this.polyStart.apply(polyIndex(pt)) + + /** The index of `param` in `params` and `less` */ + private def paramIndex(param: PolyParam): Int = { + assert(contains(param.binder)) + polyStart(param.binder) + param.paramNum + } + + /** The number of type parameters in the given entry array */ + private def paramCount(entries: Array[Type]) = entries.length >> 1 + + /** The type variable corresponding to parameter numbered `n`, null if none was created */ + private def typeVar(entries: Array[Type], n: Int): Type = + entries(paramCount(entries) + n) + + private def entry(param: PolyParam): Type = { + val entries = myMap(param.binder) + if (entries == null) NoType + else entries(param.paramNum) + } + +// ----------- Contains tests -------------------------------------------------- + + def contains(pt: PolyType): Boolean = polyIndex(pt) < polyTypes.length + + def contains(param: PolyParam): Boolean = { + val entries = myMap(param.binder) + entries != null && entries(param.paramNum).isInstanceOf[TypeBounds] + } + + def contains(tvar: TypeVar): Boolean = { + val origin = tvar.origin + val entries = myMap(origin.binder) + val pnum = origin.paramNum + entries != null && isBounds(entries(pnum)) && (typeVar(entries, pnum) eq tvar) + } + + private def isBounds(tp: Type) = tp.isInstanceOf[TypeBounds] + +// ---------- Dependency handling ---------------------------------------------- + + private def upperBits(i: Int): BitSet = less(i) + + private def lowerBits(i: Int): BitSet = + (BitSet() /: less.indices) ((bits, j) => if (less(i)(j)) bits + j else bits) + + private def minUpperBits(i: Int): BitSet = { + val all = upperBits(i) + all.filterNot(j => all.exists(k => less(k)(j))) + } + + private def minLowerBits(i: Int): BitSet = { + val all = lowerBits(i) + all.filterNot(j => all.exists(k => less(j)(k))) + } + + private def overParams(op: Int => BitSet): PolyParam => List[PolyParam] = param => + op(paramIndex(param)).toList.map(params).filter(contains) + + val upper = overParams(upperBits) + val lower = overParams(lowerBits) + val minUpper = overParams(minUpperBits) + val minLower = overParams(minLowerBits) + + +// ---------- Info related to PolyParams ------------------------------------------- + + def related(param1: PolyParam, param2: PolyParam, firstIsLower: Boolean)(implicit ctx: Context): Boolean = { + val i1 = paramIndex(param1) + val i2 = paramIndex(param2) + if (firstIsLower) less(i1)(i2) else less(i2)(i1) + } + + def nonParamBounds(param: PolyParam)(implicit ctx: Context): TypeBounds = + entry(param).asInstanceOf[TypeBounds] + + def bounds(param: PolyParam)(implicit ctx: Context): TypeBounds = { + val bounds @ TypeBounds(lo, hi) = nonParamBounds(param) + bounds.derivedTypeBounds( + (lo /: minLower(param))(OrType.apply), + (hi /: minUpper(param))(AndType.apply)) + } + + def at(param: PolyParam)(implicit ctx: Context): Type = { + entry(param) match { + case _: TypeBounds => bounds(param) + case e => e + } + } + + def typeVarOfParam(param: PolyParam): Type = { + val entries = myMap(param.binder) + if (entries == null) NoType + else { + val tvar = typeVar(entries, param.paramNum) + if (tvar != null) tvar else NoType + } + } + +// ---------- Type splitting -------------------------------------------------- + + /** The set of "dependent" constrained parameters that unconditionally strengthen bound `tp`. + * @param seenFromBelow If true, `bound` is an upper bound, else a lower bound. + */ + private def depParams(tp: Type, seenFromBelow: Boolean): Set[PolyParam] = tp match { + case tp: PolyParam if contains(tp) => + Set(tp) + case tp: AndOrType if seenFromBelow == tp.isAnd => + depParams(tp.tp1, seenFromBelow) | depParams(tp.tp2, seenFromBelow) + case _ => + Set.empty + } + + /** The bound type `tp` without dependent parameters. + * NoType if type consists only of dependent parameters. + * @param seenFromBelow If true, `bound` is an upper bound, else a lower bound. + */ + private def stripParams(tp: Type, seenFromBelow: Boolean)(implicit ctx: Context): Type = tp match { + case tp: PolyParam if contains(tp) => + NoType + case tp: AndOrType if seenFromBelow == tp.isAnd => + val tp1 = nonParamType(tp.tp1, seenFromBelow) + val tp2 = nonParamType(tp.tp2, seenFromBelow) + if (tp1.exists) + if (tp2.exists) tp.derivedAndOrType(tp1, tp2) + else tp1 + else tp2 + case _ => + tp + } + + /** The bound type `tp` without dependent parameters. + * A top or bottom type if type consists only of dependent parameters. + * @param seenFromBelow If true, `bound` is an upper bound, else a lower bound. + */ + private def nonParamType(tp: Type, seenFromBelow: Boolean)(implicit ctx: Context): Type = + stripParams(tp, seenFromBelow).orElse(if (seenFromBelow) defn.AnyType else defn.NothingType) + + /** The `tp1 is a TypeBounds type, the bounds without dependent parameters, + * otherwise `tp`. + */ + private def nonParamType(tp: Type)(implicit ctx: Context): Type = tp match { + case tp @ TypeBounds(lo, hi) => + tp.derivedTypeBounds( + nonParamType(lo, seenFromBelow = false), + nonParamType(hi, seenFromBelow = true)) + case _ => + tp + } + + /** An updated partial order matrix that incorporates `less` and also reflects the new `bounds` + * for parameter `param`. + */ + private def updatedLess(less: Array[BitSet], param: PolyParam, bounds: Type): Array[BitSet] = bounds match { + case TypeBounds(lo, hi) => + updatedLess( + updatedLess(less, param, lo, seenFromBelow = false), + param, hi, seenFromBelow = true) + case _ => + less + } + + /** An updated partial order matrix that incorporates `less` and also reflects that `param` has a new + * `bound`, where `seenFromBelow` is true iff `bound` is an upper bound for `param`. + */ + def updatedLess(less: Array[BitSet], param: PolyParam, bound: Type, seenFromBelow: Boolean): Array[BitSet] = + updatedLess(less, param, depParams(bound, seenFromBelow).iterator, inOrder = seenFromBelow) + + /** An updated partial order matrix that incorporates `less` and also reflects that `param` relates + * to all parameters in `ps2` wrt <:< if `inOrder` is true, `>:>` otherwise. + */ + def updatedLess(less: Array[BitSet], p1: PolyParam, ps2: Iterator[PolyParam], inOrder: Boolean): Array[BitSet] = + if (ps2.hasNext) updatedLess(updatedLess(less, p1, ps2.next, inOrder), p1, ps2, inOrder) + else less + + /** An updated partial order matrix that incorporates `less` and also reflects that `param` relates + * to `p2` wrt <:< if `inOrder` is true, `>:>` otherwise. + */ + def updatedLess(less: Array[BitSet], p1: PolyParam, p2: PolyParam, inOrder: Boolean): Array[BitSet] = + if (!inOrder) updatedLess(less, p2, p1, true) + else { + val i1 = paramIndex(p1) + val i2 = paramIndex(p2) + if (i1 == i2 || less(i1)(i2)) less + else { + val result = less.clone + result(i1) = result(i1) + i2 | upperBits(i2) + assert(!result(i1)(i1)) + for (j <- lowerBits(i1)) { + result(j) = result(j) + i2 | upperBits(i2) + assert(!result(j)(j)) + } + result + } + } + +// ---------- Updates ------------------------------------------------------------ + + def order(param: PolyParam, bound: PolyParam, inOrder: Boolean)(implicit ctx: Context): This = { + val less1 = updatedLess(less, param, bound, inOrder) + if (less1 eq less) this else newConstraint(myMap, less1, params) + } + + def nonParamUpdated(param: PolyParam, tpe: Type)(implicit ctx: Context): This = { + val entries1 = myMap(param.binder).clone + entries1(param.paramNum) = tpe + newConstraint(myMap.updated(param.binder, entries1), less, params) + } + + def updated(param: PolyParam, tpe: Type)(implicit ctx: Context): This = { + val less1 = updatedLess(less, param, tpe) + val entries = myMap(param.binder) + val entry1 = nonParamType(tpe) + val idx = param.paramNum + val entries1 = + if (entry1 eq entries(idx)) entries + else { + val entries1 = entries.clone + entries1(idx) = entry1 + entries1 + } + newConstraint(myMap.updated(param.binder, entries1), less1, params) + } + + /** Drop parameter `PolyParam(poly, n)` from `bounds`, + * replacing with Nothing in the lower bound and by `Any` in the upper bound. + */ + private def dropParamIn(bounds: TypeBounds, poly: PolyType, n: Int)(implicit ctx: Context): TypeBounds = { + def drop(tp: Type): Type = tp match { + case tp: AndOrType => + val tp1 = drop(tp.tp1) + val tp2 = drop(tp.tp2) + if (!tp1.exists) tp2 + else if (!tp2.exists) tp1 + else tp + case PolyParam(`poly`, `n`) => NoType + case _ => tp + } + def approx(tp: Type, limit: Type): Type = { + val tp1 = drop(tp) + if (tp1.exists || !tp.exists) tp1 else limit + } + bounds.derivedTypeBounds( + approx(bounds.lo, defn.NothingType), approx(bounds.hi, defn.AnyType)) + } + + /** A new constraint which is derived from this constraint by removing + * the type parameter `param` from the domain and replacing all occurrences + * of the parameter elsewhere in the constraint by type `tp`. + */ + def replace(param: PolyParam, tp: Type)(implicit ctx: Context): TrackingConstraint = { + val replacement = tp.dealias.stripTypeVar + + def subst(poly: PolyType, entries: Array[Type]) = { + var result = entries + var i = 0 + while (i < paramCount(entries)) { + entries(i) match { + case oldBounds: TypeBounds => + val newBounds = oldBounds.substParam(param, replacement).asInstanceOf[TypeBounds] + if (oldBounds ne newBounds) { + if (result eq entries) result = entries.clone + result(i) = dropParamIn(newBounds, poly, i) + } + case _ => + } + i += 1 + } + result + } + + if (param == replacement) this + else { + val pt = param.binder + val constr1 = if (isRemovable(pt, param.paramNum)) remove(pt) else updated(param, replacement) + val result = new TrackingConstraint(constr1.myMap mapValues subst, constr1.less, constr1.params) + if (Config.checkConstraintsNonCyclic) result.checkNonCyclic() + result + } + } + + def unify(p1: PolyParam, p2: PolyParam)(implicit ctx: Context): This = { + val p1Bounds = + dropParamIn(nonParamBounds(p1), p2.binder, p2.paramNum) & + dropParamIn(nonParamBounds(p2), p1.binder, p1.paramNum) + this.nonParamUpdated(p1, p1Bounds).nonParamUpdated(p2, p1) + } + + def add(poly: PolyType, tvars: List[TypeVar])(implicit ctx: Context): This = { + assert(!contains(poly)) + val nparams = poly.paramNames.length + val entries1 = new Array[Type](nparams * 2) + poly.paramBounds.copyToArray(entries1, 0) + tvars.copyToArray(entries1, nparams) + val is = poly.paramBounds.indices + val newParams = is.map(PolyParam(poly, _)) + val params1 = params ++ newParams + var less1 = less ++ is.map(Function.const(BitSet.empty)) + for (i <- is) { + less1 = updatedLess(less1, newParams(i), entries1(i)) + entries1(i) = nonParamType(entries1(i)) + } + newConstraint(myMap.updated(poly, entries1), less1, params1) + } + + /** A new constraint with all entries coming from `pt` removed. */ + def remove(pt: PolyType)(implicit ctx: Context): This = { + val start = polyStart(pt) + val skipped = pt.paramNames.length + + def shrinkSet(bits: BitSet): BitSet = + (BitSet() /: bits) ((res, i) => + if (i < start) res + i + else if (i < start + skipped) res + else res + (i - skipped)) + def shrinkArray[T: ClassTag](src: Array[T]) = { + val dst = new Array[T](src.length - skipped) + Array.copy(src, 0, dst, 0, start) + Array.copy(src, start + skipped, dst, start, dst.length - start) + dst + } + newConstraint( + myMap = myMap remove pt, + less = shrinkArray(less).map(shrinkSet(_)), + params = shrinkArray(params)) + } + + def isRemovable(pt: PolyType, removedParam: Int = -1): Boolean = { + val entries = myMap(pt) + var noneLeft = true + var i = paramCount(entries) + while (noneLeft && i > 0) { + i -= 1 + if (i != removedParam && isBounds(entries(i))) noneLeft = false + else typeVar(entries, i) match { + case tv: TypeVar => + if (!tv.inst.exists) noneLeft = false // need to keep line around to compute instType + case _ => + } + } + noneLeft + } + +// ---------- Exploration -------------------------------------------------------- + + def domainPolys: List[PolyType] = polyTypes.toList + + def domainParams: List[PolyParam] = params.toList + + def forallParams(p: PolyParam => Boolean): Boolean = { + myMap.foreachBinding { (poly, entries) => + for (i <- 0 until paramCount(entries)) + if (isBounds(entries(i)) && !p(PolyParam(poly, i))) return false + } + true + } + + def foreachTypeVar(op: TypeVar => Unit): Unit = + myMap.foreachBinding { (poly, entries) => + for (i <- 0 until paramCount(entries)) { + typeVar(entries, i) match { + case tv: TypeVar if !tv.inst.exists => op(tv) + case _ => + } + } + } + + private var myUninstVars: mutable.ArrayBuffer[TypeVar] = _ + + /** The uninstantiated typevars of this constraint */ + def uninstVars: collection.Seq[TypeVar] = { + if (myUninstVars == null) { + myUninstVars = new mutable.ArrayBuffer[TypeVar] + myMap.foreachBinding { (poly, entries) => + for (i <- 0 until paramCount(entries)) { + typeVar(entries, i) match { + case tv: TypeVar if isBounds(entries(i)) => myUninstVars += tv + case _ => + } + } + } + } + myUninstVars + } + +// ---------- Cyclic checking ------------------------------------------- + + private def checkNonCyclic(idx: Int)(implicit ctx: Context): Unit = + assert(!less(idx)(idx), i"cyclic constraint involving ${params(idx)}") + + def checkNonCyclic(pt: PolyType, entries: Array[Type])(implicit ctx: Context): Unit = + for (i <- entries.indices) checkNonCyclic(paramIndex(PolyParam(pt, i))) + + def checkNonCyclic()(implicit ctx: Context): Unit = + for (i <- params.indices) checkNonCyclic(i) + + def checkNonCyclicTrans()(implicit ctx: Context): Unit = checkNonCyclic() + +// ---------- toText ----------------------------------------------------- + + override def toText(printer: Printer): Text = { + def entryText(tp: Type) = tp match { + case tp: TypeBounds => + tp.toText(printer) + case _ => + " := " ~ tp.toText(printer) + } + val indent = 3 + val header: Text = "Constraint(" + val uninstVarsText = " uninstVars = " ~ + Text(uninstVars map (_.toText(printer)), ", ") ~ ";" + val constrainedText = + " constrained types = " ~ Text(domainPolys map (_.toText(printer)), ", ") + val boundsText = + " bounds = " ~ { + val assocs = + for (param <- domainParams) + yield (" " * indent) ~ param.toText(printer) ~ entryText(entry(param)) + Text(assocs, "\n") + } + val orderingText = + " ordering = " ~ { + val deps = + for { + param <- domainParams + ups = minUpper(param) + if ups.nonEmpty + } + yield + (" " * indent) ~ param.toText(printer) ~ " <: " ~ + Text(ups.map(_.toText(printer)), ", ") + Text(deps, "\n") + } + Text.lines(List(header, uninstVarsText, constrainedText, boundsText, orderingText, ")")) + } +} + diff --git a/src/dotty/tools/dotc/core/TyperState.scala b/src/dotty/tools/dotc/core/TyperState.scala index 5d910c905e82..cb8538e26a1a 100644 --- a/src/dotty/tools/dotc/core/TyperState.scala +++ b/src/dotty/tools/dotc/core/TyperState.scala @@ -17,7 +17,7 @@ class TyperState(r: Reporter) extends DotClass with Showable { def reporter = r /** The current constraint set */ - def constraint: Constraint = new NaiveConstraint(SimpleMap.Empty) + def constraint: Constraint = new NaiveConstraint(SimpleMap.Empty)//new TrackingConstraint(SimpleMap.Empty, Array(), Array()) def constraint_=(c: Constraint): Unit = {} /** The uninstantiated variables */ @@ -38,7 +38,7 @@ class TyperState(r: Reporter) extends DotClass with Showable { * is done only in a temporary way for contexts that may be retracted * without also retracting the type var as a whole. */ - def instType(tvar: TypeVar): Type = constraint.at(tvar.origin) match { + def instType(tvar: TypeVar)(implicit ctx: Context): Type = constraint.at(tvar.origin) match { case _: TypeBounds => NoType case tp: PolyParam => var tvar1 = constraint.typeVarOfParam(tp) diff --git a/src/dotty/tools/dotc/typer/ProtoTypes.scala b/src/dotty/tools/dotc/typer/ProtoTypes.scala index 98300f0b019e..e16b83afd67e 100644 --- a/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -375,7 +375,7 @@ object ProtoTypes { tp.derivedRefinedType(wildApprox(tp.parent, theMap), tp.refinedName, wildApprox(tp.refinedInfo, theMap)) case tp: TypeAlias => // default case, inlined for speed tp.derivedTypeAlias(wildApprox(tp.alias, theMap)) - case tp @ PolyParam(poly, pnum) => + case tp @ PolyParam(poly, pnum) => // !!! todo adapt to TrackingConstraint ctx.typerState.constraint.at(tp) match { case bounds: TypeBounds => wildApprox(WildcardType(bounds)) case NoType => WildcardType(wildApprox(poly.paramBounds(pnum)).bounds) From 3e3ef5222429beddedd6912feeff7975bdd67bf8 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 13 Jan 2015 17:35:12 +0100 Subject: [PATCH 39/72] Two new hooks in ConstraintHandling Unlike isSubtypeWhenFrozen, these can be implemented by TrackingConstraint without needing full bounds, which is expensive. --- src/dotty/tools/dotc/core/ConstraintHandling.scala | 8 +++++++- src/dotty/tools/dotc/core/TypeComparer.scala | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/dotty/tools/dotc/core/ConstraintHandling.scala b/src/dotty/tools/dotc/core/ConstraintHandling.scala index 2aa2a4acd246..c3243f5d9a8a 100644 --- a/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -68,8 +68,14 @@ trait ConstraintHandling { try isSubType(tp1, tp2) finally frozenConstraint = saved } + + protected def constraintImpliesSub(param: PolyParam, tp: Type): Boolean = + isSubTypeWhenFrozen(bounds(param).hi, tp) + + protected def constraintImpliesSuper(param: PolyParam, tp: Type): Boolean = + isSubTypeWhenFrozen(tp, bounds(param).lo) - /** The current bounds of type parameter `param` */ + /** The current bounds of type parameter `param` */ final def bounds(param: PolyParam): TypeBounds = constraint at param match { case bounds: TypeBounds if !ignoreConstraint => bounds case _ => param.binder.paramBounds(param.paramNum) diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index 457b29829787..d327da017e4c 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -180,7 +180,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi tp2 == tp1 || { if (solvedConstraint && (constraint contains tp2)) isSubType(tp1, bounds(tp2).lo) else - isSubTypeWhenFrozen(tp1, bounds(tp2).lo) || { + constraintImpliesSuper(tp2, tp1) || { if (isConstrained(tp2)) addConstraint(tp2, tp1.widenExpr, fromBelow = true) else (ctx.mode is Mode.TypevarsMissContext) || secondTry(tp1, tp2) } @@ -242,7 +242,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi tp1 == tp2 || { if (solvedConstraint && (constraint contains tp1)) isSubType(bounds(tp1).lo, tp2) else - isSubTypeWhenFrozen(bounds(tp1).hi, tp2) || { + constraintImpliesSub(tp1, tp2) || { if (isConstrained(tp1)) addConstraint(tp1, tp2, fromBelow = false) && { if ((!frozenConstraint) && From ef9c11f7e34b39229254353e245a53aa4d4777d0 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 14 Jan 2015 18:22:37 +0100 Subject: [PATCH 40/72] Fix after review: reset state variable needsGc Without the reset, states are gc-ed unncessssarily. --- src/dotty/tools/dotc/core/TypeComparer.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index d327da017e4c..f3518eff2d9e 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -98,7 +98,10 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi else monitoredIsSubType(tp1, tp2) recCount = recCount - 1 if (!result) constraint = saved - else if (recCount == 0 && needsGc) state.gc() + else if (recCount == 0 && needsGc) { + state.gc() + needsGc = false + } if (Stats.monitored) recordStatistics(result, savedSuccessCount) result } catch { From dec46172ea9f38e3f1d9aeaac8e2984c6ea3a878 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 14 Jan 2015 18:24:36 +0100 Subject: [PATCH 41/72] New test: moduleSubtyping Tests (non)equivalence of modules and their ThisTypes. --- test/dotc/tests.scala | 1 + tests/neg/moduleSubtyping.scala | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 tests/neg/moduleSubtyping.scala diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index 71668823a3ce..89ac2b6c4e45 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -117,6 +117,7 @@ class tests extends CompilerTest { @Test def neg_i0091_infpaths = compileFile(negDir, "i0091-infpaths", xerrors = 3) @Test def neg_i0248_inherit_refined = compileFile(negDir, "i0248-inherit-refined", xerrors = 4) @Test def neg_i0281 = compileFile(negDir, "i0281-null-primitive-conforms", xerrors = 3) + @Test def neg_moduleSubtyping = compileFile(negDir, "moduleSubtyping", xerrors = 4) @Test def dotc = compileDir(dotcDir + "tools/dotc", failedOther)(allowDeepSubtypes) @Test def dotc_ast = compileDir(dotcDir + "tools/dotc/ast", failedOther) // similar to dotc_config diff --git a/tests/neg/moduleSubtyping.scala b/tests/neg/moduleSubtyping.scala new file mode 100644 index 000000000000..18e93d5ac616 --- /dev/null +++ b/tests/neg/moduleSubtyping.scala @@ -0,0 +1,23 @@ +class C { + + object o { + + var a: C.this.o.type = ??? + var b: this.type = ??? + a = b // OK + b = a // OK + + var c: Test.o.type = ??? + a = c // error + b = c // error + c = a // error + c = b // error + } + +} + +object Test extends C { + + + +} From 1a6cc683f37ea9f57454d1d7f62ac975c98c2a94 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 14 Jan 2015 18:36:20 +0100 Subject: [PATCH 42/72] Rename isConstrained -> canConstrain for clarity Plus some more comments explaining aspects of isSubType. --- .../tools/dotc/core/ConstraintHandling.scala | 2 +- src/dotty/tools/dotc/core/TypeComparer.scala | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/dotty/tools/dotc/core/ConstraintHandling.scala b/src/dotty/tools/dotc/core/ConstraintHandling.scala index c3243f5d9a8a..96174ad1c2a6 100644 --- a/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -229,7 +229,7 @@ trait ConstraintHandling { res } - final def isConstrained(param: PolyParam): Boolean = + final def canConstrain(param: PolyParam): Boolean = !frozenConstraint && !solvedConstraint && (constraint contains param) /** Test whether the lower bounds of all parameters in this diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index f3518eff2d9e..4bb061aab2da 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -184,7 +184,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi if (solvedConstraint && (constraint contains tp2)) isSubType(tp1, bounds(tp2).lo) else constraintImpliesSuper(tp2, tp1) || { - if (isConstrained(tp2)) addConstraint(tp2, tp1.widenExpr, fromBelow = true) + if (canConstrain(tp2)) addConstraint(tp2, tp1.widenExpr, fromBelow = true) else (ctx.mode is Mode.TypevarsMissContext) || secondTry(tp1, tp2) } } @@ -246,7 +246,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi if (solvedConstraint && (constraint contains tp1)) isSubType(bounds(tp1).lo, tp2) else constraintImpliesSub(tp1, tp2) || { - if (isConstrained(tp1)) + if (canConstrain(tp1)) addConstraint(tp1, tp2, fromBelow = false) && { if ((!frozenConstraint) && (tp2 isRef defn.NothingClass) && @@ -449,6 +449,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi case tp2i: TermRef => isSubType(tp1, tp2i) case ExprType(tp2i: TermRef) if (ctx.phase.id > ctx.gettersPhase.id) => + // After getters, val x: T becomes def x: T isSubType(tp1, tp2i) case _ => false @@ -516,10 +517,15 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi 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 saved = skolemsOutstanding try { - def rebindNeeded = tp2.refinementRefersToThis + val rebindNeeded = tp2.refinementRefersToThis val base = if (rebindNeeded) ensureSingleton(tp1) else tp1 val rinfo2 = if (rebindNeeded) tp2.refinedInfo.substSkolem(tp2, base) else tp2.refinedInfo def qualifies(m: SingleDenotation) = isSubType(m.info, rinfo2) @@ -531,7 +537,9 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi memberMatches(base member name) || tp1.isInstanceOf[SingletonType] && { // special case for situations like: - // foo <: C { type T = foo.T } + // class C { type T } + // val foo: C + // foo.type <: C { type T = foo.T } rinfo2 match { case rinfo2: TypeAlias => !ctx.phase.erasedTypes && (base select name) =:= rinfo2.alias From a24f73eb145d917863958551ff3f0a7c7db2a9dc Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 14 Jan 2015 18:53:25 +0100 Subject: [PATCH 43/72] Removed redundant test in isSubType. --- src/dotty/tools/dotc/core/TypeComparer.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index 4bb061aab2da..fbb2e1b7bf55 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -541,8 +541,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi // val foo: C // foo.type <: C { type T = foo.T } rinfo2 match { - case rinfo2: TypeAlias => - !ctx.phase.erasedTypes && (base select name) =:= rinfo2.alias + case rinfo2: TypeAlias => (base select name) =:= rinfo2.alias case _ => false } } From c0b75098f66d4611683bc308d7f6127e6c6e433b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 14 Jan 2015 18:56:28 +0100 Subject: [PATCH 44/72] Fixed bug/typo in comparing prefixes of ClassInfo types. --- src/dotty/tools/dotc/core/TypeComparer.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index fbb2e1b7bf55..05edad59b9c3 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -409,7 +409,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi case ClassInfo(pre2, cls2, _, _, _) => def compareClassInfo = tp1 match { case ClassInfo(pre1, cls1, _, _, _) => - (cls1 eq cls2) && isSubType(pre2, pre1) + (cls1 eq cls2) && isSubType(pre1, pre2) case _ => false } From 19234838329aabfe202a2fbf46470528baebd83c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 14 Jan 2015 19:04:47 +0100 Subject: [PATCH 45/72] Allow to merge TypeBounds and ClassInfos in |, & There seems to be no reason why we should not merge a class and an abstract type that has the class within its bounds. I.e. Assume class A { type T } class B { class T } Then (A | B) # T should be legal and refer to type T, and (A & B) # T should be legal and refer to class T. --- src/dotty/tools/dotc/core/Symbols.scala | 3 +++ src/dotty/tools/dotc/core/TypeComparer.scala | 16 ++++++++-------- src/dotty/tools/dotc/core/Types.scala | 1 + 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/dotty/tools/dotc/core/Symbols.scala b/src/dotty/tools/dotc/core/Symbols.scala index 2be97691fe9c..2854d7d2f23a 100644 --- a/src/dotty/tools/dotc/core/Symbols.scala +++ b/src/dotty/tools/dotc/core/Symbols.scala @@ -256,6 +256,9 @@ trait Symbols { this: Context => tparams } + /** Create a new skolem symbol. This is not the same as SkolemType, even thouggh the + * motivation (create a singleton referencing to a type)= is similar. + */ def newSkolem(tp: Type) = newSymbol(defn.RootClass, nme.SKOLEM, SyntheticArtifact | Permanent, tp) def newErrorSymbol(owner: Symbol, name: Name) = diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index 05edad59b9c3..0d2dc20d3e46 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -932,14 +932,14 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi case tp1: TypeBounds => tp2 match { case tp2: TypeBounds => tp1 & tp2 + case tp2: ClassInfo if tp1 contains tp2.typeRef => tp2 case _ => andConflict(tp1, tp2) } case tp1: ClassInfo => tp2 match { - case tp2: ClassInfo if tp1.cls eq tp2.cls => - tp1.derivedClassInfo(tp1.prefix & tp2.prefix) - case _ => - andConflict(tp1, tp2) + 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 _ => andConflict(tp1, tp2) } case tp1 @ MethodType(names1, formals1) => tp2 match { @@ -997,14 +997,14 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi case tp1: TypeBounds => tp2 match { case tp2: TypeBounds => tp1 | tp2 + case tp2: ClassInfo if tp1 contains tp2.typeRef => tp1 case _ => orConflict(tp1, tp2) } case tp1: ClassInfo => tp2 match { - case tp2: ClassInfo if tp1.cls eq tp2.cls => - tp1.derivedClassInfo(tp1.prefix | tp2.prefix) - case _ => - orConflict(tp1, tp2) + 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 _ => orConflict(tp1, tp2) } case tp1 @ MethodType(names1, formals1) => tp2 match { diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 0363a8c706b7..8249e5a4c37f 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -2151,6 +2151,7 @@ object Types { } } + /** TODO Some docs would be nice here! */ case class PolyParam(binder: PolyType, paramNum: Int) extends ParamType { type BT = PolyType def copyBoundType(bt: BT) = PolyParam(bt, paramNum) From 566dd6e8a44090168ebe8e6703fea27152802286 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 14 Jan 2015 19:06:25 +0100 Subject: [PATCH 46/72] Fix skolemization logic. The skolemsOutstanding flag was reset too early. Just de-skolemizing one type does not guarantee that all further types are skolem-free. --- src/dotty/tools/dotc/core/Skolemization.scala | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/dotty/tools/dotc/core/Skolemization.scala b/src/dotty/tools/dotc/core/Skolemization.scala index d9e35af9dead..2e517c90e802 100644 --- a/src/dotty/tools/dotc/core/Skolemization.scala +++ b/src/dotty/tools/dotc/core/Skolemization.scala @@ -33,10 +33,7 @@ trait Skolemization { * else return the largest subtype. */ final def deSkolemize(tp: Type, toSuper: Boolean): Type = - if (skolemsOutstanding) { - skolemsOutstanding = false - deSkolemize(tp, if (toSuper) 1 else -1, Set()) - } + if (skolemsOutstanding) deSkolemize(tp, if (toSuper) 1 else -1, Set()) else tp private def deSkolemize(tp: Type, variance: Int, seen: Set[SkolemType]): Type = From fbe4171404ac56a0fe8e6d54fa06bbd53e85bd97 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 18 Jan 2015 18:35:02 +0100 Subject: [PATCH 47/72] New constraint handling New constraint handling scheme using constraints that distinguish more between parameter and non-parameter bounds and which track parameter bounds separately. This allows a co-inductive formulation of satisfiability checking without fishy cyclicity checks. It should also scale better for long chains of dependent type variables. --- src/dotty/tools/dotc/config/Config.scala | 6 - .../tools/dotc/config/ScalaSettings.scala | 2 +- src/dotty/tools/dotc/core/Constants.scala | 2 +- src/dotty/tools/dotc/core/Constraint.scala | 91 ++-- .../tools/dotc/core/ConstraintHandling.scala | 394 +++++++++--------- .../tools/dotc/core/NaiveConstraint.scala | 378 ----------------- .../tools/dotc/core/TrackingConstraint.scala | 283 ++++++------- src/dotty/tools/dotc/core/TypeComparer.scala | 32 +- src/dotty/tools/dotc/core/TyperState.scala | 4 +- src/dotty/tools/dotc/core/Types.scala | 2 +- .../tools/dotc/printing/PlainPrinter.scala | 8 +- src/dotty/tools/dotc/typer/Inferencing.scala | 2 +- src/dotty/tools/dotc/typer/ProtoTypes.scala | 3 +- 13 files changed, 382 insertions(+), 825 deletions(-) delete mode 100644 src/dotty/tools/dotc/core/NaiveConstraint.scala diff --git a/src/dotty/tools/dotc/config/Config.scala b/src/dotty/tools/dotc/config/Config.scala index 29d5f7922cbd..81c1af5c9a84 100644 --- a/src/dotty/tools/dotc/config/Config.scala +++ b/src/dotty/tools/dotc/config/Config.scala @@ -58,12 +58,6 @@ object Config { /** The recursion depth for showing a summarized string */ final val summarizeDepth = 2 - /** Track dependencies for constraint propagation satisfiability checking - * If turned off, constraint checking is simpler but potentially slower - * for large constraints. - */ - final val trackConstrDeps = true - /** Check that variances of lambda arguments match the * variance of the underlying lambda class. */ diff --git a/src/dotty/tools/dotc/config/ScalaSettings.scala b/src/dotty/tools/dotc/config/ScalaSettings.scala index 35c3ac16ada4..281a4b6c751f 100644 --- a/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -167,7 +167,7 @@ class ScalaSettings extends Settings.SettingGroup { val Yrepldebug = BooleanSetting("-Yrepl-debug", "Trace all repl activity.") val Ytyperdebug = BooleanSetting("-Ytyper-debug", "Trace all type assignments.") val Ypatmatdebug = BooleanSetting("-Ypatmat-debug", "Trace pattern matching translation.") - val Yexplainlowlevel = BooleanSetting("-Yexplainlowlevel", "When explaining type errors, show types at a lower level.") + val Yexplainlowlevel = BooleanSetting("-Yexplain-lowlevel", "When explaining type errors, show types at a lower level.") val YnoDoubleBindings = BooleanSetting("-YnoDoubleBindings", "Assert no namedtype is bound twice (should be enabled only if program is error-free).") val optimise = BooleanSetting("-optimise", "Generates faster bytecode by applying optimisations to the program") withAbbreviation "-optimize" diff --git a/src/dotty/tools/dotc/core/Constants.scala b/src/dotty/tools/dotc/core/Constants.scala index 48c832c4bf3b..61a23bb9e33e 100644 --- a/src/dotty/tools/dotc/core/Constants.scala +++ b/src/dotty/tools/dotc/core/Constants.scala @@ -169,7 +169,7 @@ object Constants { def convertTo(pt: Type)(implicit ctx: Context): Constant = { def lowerBound(pt: Type): Type = pt.dealias.stripTypeVar match { case tref: TypeRef if !tref.symbol.isClass => lowerBound(tref.info.bounds.lo) - case param: PolyParam => lowerBound(ctx.typeComparer.bounds(param).lo) + case param: PolyParam => lowerBound(ctx.typerState.constraint.nonParamBounds(param).lo) case pt => pt } val target = lowerBound(pt).typeSymbol diff --git a/src/dotty/tools/dotc/core/Constraint.scala b/src/dotty/tools/dotc/core/Constraint.scala index f7e7a2e13601..10e6d0d5a997 100644 --- a/src/dotty/tools/dotc/core/Constraint.scala +++ b/src/dotty/tools/dotc/core/Constraint.scala @@ -31,30 +31,46 @@ abstract class Constraint extends Showable { /** Does this constraint contain the type variable `tvar` and is it uninstantiated? */ def contains(tvar: TypeVar): Boolean - /** The constraint for given type parameter `param`, or NoType if `param` is not part of + /** The constraint entry for given type parameter `param`, or NoType if `param` is not part of * the constraint domain. */ - def at(param: PolyParam)(implicit ctx: Context): Type + def entry(param: PolyParam): Type /** The type variable corresponding to parameter `param`, or * NoType, if `param` is not in constrained or is not paired with a type variable. */ def typeVarOfParam(param: PolyParam): Type - + + /** Is it known that `param1 <:< param2`? */ + def isLess(param1: PolyParam, param2: PolyParam)(implicit ctx: Context): Boolean + + /** The parameters that are known to be smaller wrt <: than `param` */ + def lower(param: PolyParam): List[PolyParam] + + /** The parameters that are known to be greater wrt <: than `param` */ + def upper(param: PolyParam): List[PolyParam] + + /** lower(param) \ lower(butNot) */ + def exclusiveLower(param: PolyParam, butNot: PolyParam): List[PolyParam] + + /** upper(param) \ upper(butNot) */ + def exclusiveUpper(param: PolyParam, butNot: PolyParam): List[PolyParam] + /** The constraint bounds for given type parameter `param`. + * Poly params that are known to be smaller or greater than `param` + * are not contained in the return bounds. * @pre `param` is not part of the constraint domain. */ - def bounds(param: PolyParam)(implicit ctx: Context): TypeBounds + def nonParamBounds(param: PolyParam): TypeBounds + + /** The lower bound of `param` including all known-to-be-smaller parameters */ + def fullLowerBound(param: PolyParam)(implicit ctx: Context): Type - /** The non-parameter bounds of this constraint. - * Poly params that are `related` are not contained in the return bounds. - */ - def nonParamBounds(param: PolyParam)(implicit ctx: Context): TypeBounds + /** The upper bound of `param` including all known-to-be-greater parameters */ + def fullUpperBound(param: PolyParam)(implicit ctx: Context): Type - /** If `firstIsLower` is `param1 <:< param2`? - * Otherwise, is `param2 <:< param1`? - */ - def related(param1: PolyParam, param2: PolyParam, firstIsLower: Boolean)(implicit ctx: Context): Boolean + /** The bounds of `param` including all known-to-be-smaller and -greater parameters */ + def fullBounds(param: PolyParam)(implicit ctx: Context): TypeBounds /** A new constraint which is derived from this constraint by adding * entries for all type parameters of `poly`. @@ -66,26 +82,28 @@ abstract class Constraint extends Showable { def add(poly: PolyType, tvars: List[TypeVar])(implicit ctx: Context): This /** A new constraint which is derived from this constraint by updating - * the entry for parameter `param` to `tpe`. - * @pre `this contains param`. - * + * the entry for parameter `param` to `tp`. * `tp` can be one of the following: * * - A TypeBounds value, indicating new constraint bounds * - Another type, indicating a solution for the parameter + * + * @pre `this contains param`. */ - def updated(param: PolyParam, tp: Type)(implicit ctx: Context): This + def updateEntry(param: PolyParam, tp: Type)(implicit ctx: Context): This - /** A constraint that includes a the relationship `param <: bound` if `inOrder` is true - * or else `bound <: param` if `inOrdxer` is false. + /** A constraint that includes the relationship `p1 <: p2` */ - def order(param: PolyParam, bound: PolyParam, inOrder: Boolean)(implicit ctx: Context): This + def addLess(p1: PolyParam, p2: PolyParam)(implicit ctx: Context): This + + /** A constraint resulting by adding p2 = p1 to this constraint, and at the same + * time transferring all bounds of p2 to p1 + */ + def unify(p1: PolyParam, p2: PolyParam)(implicit ctx: Context): This /** A new constraint which is derived from this constraint by removing * the type parameter `param` from the domain and replacing all occurrences * of the parameter elsewhere in the constraint by type `tp`. - * If `tp` is another polyparam, applies the necessary unifications to avoid a cyclic - * constraint. */ def replace(param: PolyParam, tp: Type)(implicit ctx: Context): This @@ -99,11 +117,6 @@ abstract class Constraint extends Showable { /** A new constraint with all entries coming from `pt` removed. */ def remove(pt: PolyType)(implicit ctx: Context): This - /** A constraint resulting by adding p2 = p1 to this constraint, and at the same - * time transferring all bounds of p2 to p1 - */ - def unify(p1: PolyParam, p2: PolyParam)(implicit ctx: Context): This - /** The polytypes constrained by this constraint */ def domainPolys: List[PolyType] @@ -121,32 +134,6 @@ abstract class Constraint extends Showable { /** The uninstantiated typevars of this constraint */ def uninstVars: collection.Seq[TypeVar] - /** Check that no constrained parameter in `pt` contains itself as a bound */ - def checkNonCyclic(pt: PolyType, entries: Array[Type])(implicit ctx: Context): Unit - /** Check that no constrained parameter contains itself as a bound */ def checkNonCyclic()(implicit ctx: Context): Unit - - /** Check that no constrained parameter contains itself as a bound, - * either directly or indirectly. This should be not a stricter criterion - * than checkNonCyclic because transitivity should be eliminated always, - * but it's good to be paranoid. - */ - def checkNonCyclicTrans()(implicit ctx: Context): Unit - - protected def splitParams(tp: Type, seenFromBelow: Boolean, handleParam: PolyParam => Unit)(implicit ctx: Context): Type = tp match { - case tp: PolyParam if contains(tp) => - handleParam(tp) - NoType - case tp: AndOrType if seenFromBelow == tp.isAnd => - val tp1 = splitParams(tp.tp1, seenFromBelow, handleParam) - val tp2 = splitParams(tp.tp2, seenFromBelow, handleParam) - if (tp1.exists) - if (tp2.exists) tp.derivedAndOrType(tp1, tp2) - else tp1 - else if (tp2.exists) tp2 - else NoType - case _ => - tp - } } diff --git a/src/dotty/tools/dotc/core/ConstraintHandling.scala b/src/dotty/tools/dotc/core/ConstraintHandling.scala index 96174ad1c2a6..530bbfce4baa 100644 --- a/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -36,219 +36,118 @@ trait ConstraintHandling { implicit val ctx: Context def isSubType(tp1: Type, tp2: Type): Boolean - def deSkolemize(tp: Type, toSuper: Boolean): Type val state: TyperState import state.constraint + + private var addConstraintInvocations = 0 /** If the constraint is frozen we cannot add new bounds to the constraint. */ protected var frozenConstraint = false - /** If the constraint is ignored, subtype checks only take into account - * declared bounds of PolyParams. Used when forming unions and intersectons - * of constraint bounds - */ - private var ignoreConstraint = false - - private def ignoringConstraint[T](op: => T): T = { - val savedIgnore = ignoreConstraint - val savedFrozen = frozenConstraint - ignoreConstraint = true - frozenConstraint = true - try op - finally { - ignoreConstraint = savedIgnore - frozenConstraint = savedFrozen + private def addOneUpperBound(param: PolyParam, bound: Type): Boolean = + constraint.entry(param) match { + case oldBounds @ TypeBounds(lo, hi) => + val newHi = hi & bound + (newHi eq hi) || { + val newBounds = oldBounds.derivedTypeBounds(lo, newHi) + constraint = constraint.updateEntry(param, newBounds) + isSubType(lo, newHi) + } + case _ => + true + } + + private def addOneLowerBound(param: PolyParam, bound: Type): Boolean = + constraint.entry(param) match { + case oldBounds @ TypeBounds(lo, hi) => + val newLo = lo | bound + (newLo eq lo) || { + val newBounds = oldBounds.derivedTypeBounds(newLo, hi) + constraint = constraint.updateEntry(param, newBounds) + isSubType(newLo, hi) + } + case _ => + true + } + + protected def addUpperBound(param: PolyParam, bound: Type): Boolean = { + def description = i"constraint $param <: $bound to\n$constraint" + if (bound.isRef(defn.NothingClass) && ctx.typerState.isGlobalCommittable) { + def msg = s"!!! instantiated to Nothing: $param, constraint = ${constraint.show}" + if (Config.flagInstantiationToNothing) assert(false, msg) + else ctx.log(msg) } + constr.println(i"adding $description") + val lower = constraint.lower(param) + val res = addOneUpperBound(param, bound) && lower.forall(addOneUpperBound(_, bound)) + constr.println(i"added $description = $res") + res } - - protected def isSubTypeWhenFrozen(tp1: Type, tp2: Type): Boolean = { - val saved = frozenConstraint - frozenConstraint = true - try isSubType(tp1, tp2) - finally frozenConstraint = saved + + protected def addLowerBound(param: PolyParam, bound: Type): Boolean = { + def description = i"constraint $param >: $bound to\n$constraint" + constr.println(i"adding $description") + val upper = constraint.upper(param) + val res = addOneLowerBound(param, bound) && upper.forall(addOneLowerBound(_, bound)) + constr.println(i"added $description = $res") + res } - - protected def constraintImpliesSub(param: PolyParam, tp: Type): Boolean = - isSubTypeWhenFrozen(bounds(param).hi, tp) - - protected def constraintImpliesSuper(param: PolyParam, tp: Type): Boolean = - isSubTypeWhenFrozen(tp, bounds(param).lo) - - /** The current bounds of type parameter `param` */ - final def bounds(param: PolyParam): TypeBounds = constraint at param match { - case bounds: TypeBounds if !ignoreConstraint => bounds - case _ => param.binder.paramBounds(param.paramNum) + + protected def addLess(p1: PolyParam, p2: PolyParam): Boolean = { + def description = i"ordering $p1 <: $p2 to\n$constraint" + val res = + if (constraint.isLess(p2, p1)) unify(p2, p1) + // !!! this is direction dependent - unify(p1, p2) makes i94-nada loop forever. + // Need to investigate why this is the case. + // The symptom is that we get a subtyping constraint of the form P { ... } <: P + else { + val down1 = p1 :: constraint.exclusiveLower(p1, p2) + val up2 = p2 :: constraint.exclusiveUpper(p2, p1) + val lo1 = constraint.nonParamBounds(p1).lo + val hi2 = constraint.nonParamBounds(p2).hi + constr.println(i"adding $description down1 = $down1, up2 = $up2") + constraint = constraint.addLess(p1, p2) + down1.forall(addOneUpperBound(_, hi2)) && up2.forall(addOneLowerBound(_, lo1)) + } + constr.println(i"added $description = $res") + res } - - /** Compare a solution of the constraint instead of the constrained parameters. - * The solution maps every parameter to its lower bound. + + /** Make p2 = p1, transfer all bounds of p2 to p1 + * @pre less(p1)(p2) */ - protected var solvedConstraint = false - - /** The parameters currently being constrained by addConstraint */ - private var pendingParams: Set[PolyParam] = Set() - - /** Make p2 = p1, transfer all bounds of p2 to p1 */ private def unify(p1: PolyParam, p2: PolyParam): Boolean = { constr.println(s"unifying $p1 $p2") - val constraint1 = constraint.unify(p1, p2) - val bounds = constraint1.bounds(p1) - isSubType(bounds.lo, bounds.hi) && { constraint = constraint1; true } + val down = constraint.exclusiveLower(p2, p1) + val up = constraint.exclusiveUpper(p1, p2) + constraint = constraint.unify(p1, p2) + val bounds = constraint.nonParamBounds(p1) + val lo = bounds.lo + val hi = bounds.hi + isSubType(lo, hi) && + down.forall(addOneUpperBound(_, hi)) && + up.forall(addOneLowerBound(_, lo)) } - - /** If current constraint set is not frozen, add the constraint - * - * param >: bound if fromBelow is true - * param <: bound otherwise - * - * to the bounds of `param`. If `bound` is itself a constrained parameter, also - * add the dual constraint to `bound`. - * @pre `param` is in the constraint's domain - * @return Whether the augmented constraint is still satisfiable. - */ - def addConstraint(param: PolyParam, bound0: Type, fromBelow: Boolean): Boolean = { - - /** Add bidirectional constraint. If new constraint implies 'A <: B' we also - * make sure 'B >: A' gets added and vice versa. Furthermore, if the constraint - * implies 'A <: B <: A', A and B get unified. - */ - def addc(param: PolyParam, bound: Type, fromBelow: Boolean): Boolean = { - val pbounds0 = constraint.bounds(param) - bound match { - case bound: PolyParam if constraint contains bound => - val bbounds0 @ TypeBounds(lo, hi) = constraint.bounds(bound) - if (lo eq hi) - addc(param, lo, fromBelow) - else if (param == bound) - true - else if (fromBelow && param.occursIn(lo, fromBelow = true)) - unify(param, bound) - else if (!fromBelow && param.occursIn(hi, fromBelow = false)) - unify(bound, param) - else { - val pbounds = prepare(param, bound, fromBelow) - val bbounds = prepare(bound, param, !fromBelow) - pbounds.exists && bbounds.exists && { - install(param, pbounds.bounds, pbounds0) - install(bound, bbounds.bounds, bbounds0) - true - } - } - case bound: AndOrType if fromBelow != bound.isAnd => - addc(param, bound.tp1, fromBelow) && - addc(param, bound.tp2, fromBelow) - case bound: WildcardType => - true - case bound => // !!! remove to keep the originals - val pbounds = prepare(param, bound, fromBelow) - pbounds.exists && { - install(param, pbounds.bounds, pbounds0) - true - } - } - } - - /** Install bounds for param */ - def install(param: PolyParam, newBounds: TypeBounds, oldBounds: TypeBounds): Unit = { - val curBounds = constraint.bounds(param) - try { - constraint = constraint.updated(param, newBounds) - } catch { - case ex: AssertionError => - println(i"error while updating $param $newBounds\n$constraint") - throw ex - } - if (curBounds ne oldBounds) { - // In this case the bounds were updated previously by a recursive isSubType in - // the satisfiability check of prepare. Reapply the previously added bounds, but - // go through a full addConstraint in order to eliminate any cyclic dependencies - // via unification. - if (!ignoringConstraint(isSubType(curBounds.lo, newBounds.lo))) - addConstraint(param, curBounds.lo, fromBelow) - if (!ignoringConstraint(isSubType(newBounds.hi, curBounds.hi))) - addConstraint(param, curBounds.hi, !fromBelow) - } - } - - /** Compute new bounds for `param` and check whether they are - * satisfiable. The check might in turn trigger other additions to the constraint. - * @return The new bounds for `param` (which are not installed yet), or - * NoType, if the new constraint would not be satisfiable. - */ - def prepare(param: PolyParam, bound: Type, fromBelow: Boolean): Type = { - constr.println(s"prepare ${param.show} ${if (fromBelow) ">:>" else "<:<"} ${bound.show}") - val oldBounds = constraint.bounds(param) - val newBounds = ignoringConstraint { - if (fromBelow) oldBounds.derivedTypeBounds(oldBounds.lo | bound, oldBounds.hi) - else oldBounds.derivedTypeBounds(oldBounds.lo, oldBounds.hi & bound) - } - val ok = - (param == bound) || - (oldBounds eq newBounds) || - { - if (pendingParams contains param) { - // Why the pendingParams test? It is possible that recursive subtype invocations - // come back with another constraint for `param`. An example came up when compiling - // ElimRepeated where we got the constraint - // - // Coll <: IterableLike[Tree, Coll] - // - // and added - // - // List[Tree] <: Coll - // - // The recursive bounds test is then - // - // List[Tree] <: IterableLike[Tree, Coll] - // - // and because of the F-bounded polymorphism in the supertype of List, - // i.e. List[T] <: IterableLike[T, List[T]], this leads again to - // - // List[Tree] <: Coll - // - // If a parameter is already pending, we avoid revisiting it here. - // Instead we combine the bounds computed here with the originally - // computed bounds when installing the original type. - constr.println(i"deferred bounds: $param $newBounds") - true - } else { - pendingParams += param - try isSubType(newBounds.lo, newBounds.hi) - finally pendingParams -= param - } - } - if (ok) newBounds else NoType - } - val bound = deSkolemize(bound0, toSuper = fromBelow).dealias.stripTypeVar - def description = s"${param.show} ${if (fromBelow) ">:>" else "<:<"} ${bound.show} to ${constraint.show}" - constr.println(s"adding $description") - val res = addc(param, bound, fromBelow) - constr.println(s"added $description") - if (Config.checkConstraintsNonCyclicTrans) constraint.checkNonCyclicTrans() - res + + protected final def isSubTypeWhenFrozen(tp1: Type, tp2: Type): Boolean = { + val saved = frozenConstraint + frozenConstraint = true + try isSubType(tp1, tp2) + finally frozenConstraint = saved } - final def canConstrain(param: PolyParam): Boolean = - !frozenConstraint && !solvedConstraint && (constraint contains param) - /** Test whether the lower bounds of all parameters in this * constraint are a solution to the constraint. */ - final def isSatisfiable: Boolean = { - val saved = solvedConstraint - solvedConstraint = true - try - constraint.forallParams { param => - val TypeBounds(lo, hi) = constraint.at(param) - isSubType(lo, hi) || { - ctx.log(i"sub fail $lo <:< $hi") - ctx.log(i"approximated = ${approxParams(lo)} <:< ${approxParams(hi)}") - false - } + protected final def isSatisfiable: Boolean = + constraint.forallParams { param => + val TypeBounds(lo, hi) = constraint.entry(param) + isSubType(lo, hi) || { + ctx.log(i"sub fail $lo <:< $hi") + false } - finally solvedConstraint = saved - } + } /** Solve constraint set for given type parameter `param`. * If `fromBelow` is true the parameter is approximated by its lower bound, @@ -269,22 +168,113 @@ trait ConstraintHandling { } } } - val bounds = constraint.bounds(param) - val bound = if (fromBelow) bounds.lo else bounds.hi + val bound = if (fromBelow) constraint.fullLowerBound(param) else constraint.fullUpperBound(param) val inst = avoidParam(bound) typr.println(s"approx ${param.show}, from below = $fromBelow, bound = ${bound.show}, inst = ${inst.show}") inst } - /** Map that approximates each param in constraint by its lower bound. - * Currently only used for diagnostics. + /** Constraint `c1` subsumes constraint `c2`, if under `c2` as constraint we have + * for all poly params `p` defined in `c2` as `p >: L2 <: U2`: + * + * c1 defines p with bounds p >: L1 <: U1, and + * L2 <: L1, and + * U1 <: U2 + * + * Both `c1` and `c2` are required to derive from constraint `pre`, possibly + * narrowing it with further bounds. */ - final def approxParams = new TypeMap { // !!! Dotty problem: Turn this def into a val => -Ycheck:mix fails - def apply(tp: Type): Type = tp.stripTypeVar match { - case tp: PolyParam if constraint contains tp => - this(constraint.bounds(tp).lo) - case tp => - mapOver(tp) + protected final def subsumes(c1: Constraint, c2: Constraint, pre: Constraint): Boolean = + if (c2 eq pre) true + else if (c1 eq pre) false + else { + val saved = constraint + try + c2.forallParams(p => + c1.contains(p) && + c2.upper(p).forall(c1.isLess(p, _)) && + isSubTypeWhenFrozen(c1.nonParamBounds(p), c2.nonParamBounds(p))) + finally constraint = saved + } + + protected def solvedConstraint = false + + /** The current bounds of type parameter `param` */ + final def bounds(param: PolyParam): TypeBounds = constraint.entry(param) match { + case bounds: TypeBounds => bounds + case _ => param.binder.paramBounds(param.paramNum) + } + + def initialize(pt: PolyType): Boolean = { + //println(i"INIT**! $pt") + checkPropagated(i"initialized $pt") { + pt.paramNames.indices.forall { i => + val param = PolyParam(pt, i) + val bounds = constraint.nonParamBounds(param) + val lower = constraint.lower(param) + val upper = constraint.upper(param) + if (lower.nonEmpty && !bounds.lo.isRef(defn.NothingClass) || + upper.nonEmpty && !bounds.hi.isRef(defn.AnyClass)) println(i"INIT*** $pt") + lower.forall(addOneUpperBound(_, bounds.hi)) && + upper.forall(addOneLowerBound(_, bounds.lo)) + } + } + } + + protected def constraintImpliesSub(param: PolyParam, tp: Type): Boolean = + isSubTypeWhenFrozen(bounds(param).hi, tp) + + protected def constraintImpliesSuper(param: PolyParam, tp: Type): Boolean = + isSubTypeWhenFrozen(tp, bounds(param).lo) + + final def canConstrain(param: PolyParam): Boolean = + !frozenConstraint && !solvedConstraint && (constraint contains param) + + protected def addConstraint(param: PolyParam, bound: Type, fromBelow: Boolean): Boolean = { + def description = i"constr $param ${if (fromBelow) ">:" else "<:"} $bound:\n$constraint" + checkPropagated(s"adding $description")(true) + checkPropagated(s"added $description") { + addConstraintInvocations += 1 + try bound.dealias.stripTypeVar match { + case bound: PolyParam if constraint contains bound => + if (fromBelow) addLess(bound, param) else addLess(param, bound) + case bound: AndOrType if fromBelow != bound.isAnd => + addConstraint(param, bound.tp1, fromBelow) && addConstraint(param, bound.tp2, fromBelow) + case bound: WildcardType => + true + case bound: ErrorType => + true + case _ => + if (occursAtToplevel(param, bound)) fromBelow + else if (fromBelow) addLowerBound(param, bound) + else addUpperBound(param, bound) + } + finally addConstraintInvocations -= 1 + } + } + + private def occursAtToplevel(param: Type, tp: Type): Boolean = tp match { + case tp: PolyParam => + param == tp + case bound: TypeProxy => + occursAtToplevel(param, bound.underlying) + case bound: AndOrType => + occursAtToplevel(param, bound.tp1) || occursAtToplevel(param, bound.tp2) + case _ => + false + } + + def checkPropagated(msg: => String)(result: Boolean): Boolean = { + if (result && addConstraintInvocations == 0) { + frozenConstraint = true + for (p <- constraint.domainParams) { + for (u <- constraint.upper(p)) + assert(bounds(p).hi <:< bounds(u).hi, i"propagation failure for upper $p subsumes $u\n$msg") + for (l <- constraint.lower(p)) + assert(bounds(l).lo <:< bounds(p).lo, i"propagation failure for lower $p subsumes $l\n$msg") + } + frozenConstraint = false } + result } } diff --git a/src/dotty/tools/dotc/core/NaiveConstraint.scala b/src/dotty/tools/dotc/core/NaiveConstraint.scala deleted file mode 100644 index ae5d5b0f1593..000000000000 --- a/src/dotty/tools/dotc/core/NaiveConstraint.scala +++ /dev/null @@ -1,378 +0,0 @@ -package dotty.tools -package dotc -package core - -import Types._, Contexts._, Symbols._ -import util.SimpleMap -import collection.mutable -import printing.{Printer, Showable} -import printing.Texts._ -import config.Config -import config.Printers._ - -object NaiveConstraint { - - /** The type of `Constraint#myMap` */ - type ParamInfo = SimpleMap[PolyType, Array[Type]] - - /** The type of `Constraint#dependents */ - type DependentMap = SimpleMap[PolyType, Array[Set[PolyParam]]] - - /** The type of functions that include or exclude a `PolyParam` in or from a set*/ - private type DepDelta = (Set[PolyParam], PolyParam) => Set[PolyParam] - - private val addDep: DepDelta = (_ + _) - private val removeDep: DepDelta = (_ - _) - - private val NoTypeBounds = new TypeBounds(WildcardType, WildcardType) { - override def computeHash = unsupported("computeHash") - } - - /** An accumulator that changes dependencies on `param`. - * @param param The parameter to which changed dependencies refer. - * @param ofVariance Include `PolyParams` occurring at this variance in the dependencies. - * @param delta The dependency change to perform (add or remove). - */ - private class ChangeDependencies(param: PolyParam, ofVariance: Int, delta: DepDelta)(implicit ctx: Context) - extends TypeAccumulator[DependentMap] { - def apply(deps: DependentMap, tp: Type): DependentMap = tp match { - case tp @ PolyParam(pt, n) if - this.variance == 0 || this.variance == ofVariance => - val oldDeps = deps(pt) - val original = safeSelect(oldDeps, n) - val changed = delta(original, param) - if (original eq changed) deps - else { - val newDeps = - if (oldDeps == null) new Array[Set[PolyParam]](pt.paramBounds.length) - else oldDeps.clone - newDeps(n) = changed - deps.updated(pt, newDeps) - } - case _ => foldOver(deps, tp) - } - } - - /** `deps(n)`, except that `Set()` is returned if `deps` or `deps(n)` are null */ - private def safeSelect(deps: Array[Set[PolyParam]], n: Int) : Set[PolyParam] = - if (deps == null || deps(n) == null) Set() - else deps(n) - - private def ignoreParam(p: PolyParam): Unit = {} -} - -import NaiveConstraint._ - -/** Constraint over undetermined type parameters - * @param myMap a map from PolyType to arrays. - * Each array contains twice the number of entries as there a type parameters - * in the PolyType. The first half of the array contains the type bounds that constrain the - * polytype's type parameters. The second half might contain type variables that - * track the corresponding parameters, or is left empty (filled with nulls). - * An instantiated type parameter is represented by having its instance type in - * the corresponding array entry. - */ -class NaiveConstraint(private val myMap: ParamInfo) extends Constraint { - - type This = NaiveConstraint - - def contains(pt: PolyType): Boolean = myMap(pt) != null - - def contains(param: PolyParam): Boolean = { - val entries = myMap(param.binder) - entries != null && entries(param.paramNum).isInstanceOf[TypeBounds] - } - - def contains(tvar: TypeVar): Boolean = { - val origin = tvar.origin - val entries = myMap(origin.binder) - val pnum = origin.paramNum - entries != null && isBounds(entries(pnum)) && (typeVar(entries, pnum) eq tvar) - } - - /** The number of type parameters in the given entry array */ - private def paramCount(entries: Array[Type]) = entries.length >> 1 - - /** The type variable corresponding to parameter numbered `n`, null if none was created */ - private def typeVar(entries: Array[Type], n: Int): Type = - entries(paramCount(entries) + n) - - private def isBounds(tp: Type) = tp.isInstanceOf[TypeBounds] - - def at(param: PolyParam)(implicit ctx: Context): Type = { - val entries = myMap(param.binder) - if (entries == null) NoType else entries(param.paramNum) - } - - def entry(param: PolyParam) = { - val entries = myMap(param.binder) - if (entries == null) NoType else entries(param.paramNum) - } - - def bounds(param: PolyParam)(implicit ctx: Context): TypeBounds = at(param).asInstanceOf[TypeBounds] - - def nonParamBounds(param: PolyParam)(implicit ctx: Context): TypeBounds = { - val bs @ TypeBounds(lo, hi) = bounds(param) - val lo1 = splitParams(lo, seenFromBelow = false, ignoreParam) - val hi1 = splitParams(hi, seenFromBelow = true, ignoreParam) - bs.derivedTypeBounds(lo1.orElse(defn.NothingType), hi1.orElse(defn.AnyType)) - } - - def related(param1: PolyParam, param2: PolyParam, inOrder: Boolean)(implicit ctx: Context): Boolean = { - var isRelated = false - def registerParam(p: PolyParam) = if (p == param2) isRelated = true - val TypeBounds(lo, hi) = bounds(param1) - if (inOrder) splitParams(hi, seenFromBelow = true, registerParam) - else splitParams(lo, seenFromBelow = false, registerParam) - isRelated - } - - def typeVarOfParam(param: PolyParam): Type = { - val entries = myMap(param.binder) - if (entries == null) NoType - else { - val tvar = typeVar(entries, param.paramNum) - if (tvar != null) tvar else NoType - } - } - - /** A new constraint which is derived from this constraint by adding or replacing - * the entries corresponding to `pt` with `entries`. - */ - private def updateEntries(pt: PolyType, entries: Array[Type])(implicit ctx: Context) : NaiveConstraint = { - val res = new NaiveConstraint(myMap.updated(pt, entries)) - - //assert(res.domainPolys.filter(pt => - // pt.resultType.resultType.widen.classSymbol.name.toString == "Ensuring").length < 2) //DEBUG - if (Config.checkConstraintsNonCyclic) checkNonCyclic(pt, entries) - ctx.runInfo.recordConstraintSize(res, res.myMap.size) - res - } - - def checkNonCyclic(pt: PolyType, entries: Array[Type])(implicit ctx: Context): Unit = - for ((entry, i) <- entries.zipWithIndex) { - val param = PolyParam(pt, i) - entry match { - case TypeBounds(lo, hi) => - assert(!param.occursIn(lo, fromBelow = true), s"$param occurs below $lo") - assert(!param.occursIn(hi, fromBelow = false), s"$param occurs above $hi") - case _ => - } - } - - def checkNonCyclic()(implicit ctx: Context): Unit = { - for (pt <- domainPolys) checkNonCyclic(pt, myMap(pt)) - } - - /** Check that no constrained parameter contains itself as a bound, - * either directly or indirectly. This should be not a structer criterion - * than checkNonCyclic because transitivity should be eliminated always, - * but it's good to be paranoid. - */ - def checkNonCyclicTrans()(implicit ctx: Context): Unit = { - for (pt <- domainPolys) - checkNonCyclicTrans(pt, myMap(pt)) - } - - private def checkNonCyclicTrans(pt: PolyType, entries: Array[Type])(implicit ctx: Context): Unit = - for ((entry, i) <- entries.zipWithIndex) { - def occursIn(params: Set[PolyParam], bound: Type, fromBelow: Boolean): Boolean = bound.stripTypeVar match { - case bound: PolyParam => - params.contains(bound) || { - at(bound) match { - case TypeBounds(lo, hi) => - occursIn(params + bound, if (fromBelow) lo else hi, fromBelow) - case _ => - false - } - } - case bound: AndOrType => - def occ1 = occursIn(params, bound.tp1, fromBelow) - def occ2 = occursIn(params, bound.tp2, fromBelow) - if (fromBelow == bound.isAnd) occ1 && occ2 else occ1 || occ2 - case _ => false - } - val param = PolyParam(pt, i) - entry match { - case TypeBounds(lo, hi) => - assert(!occursIn(Set(param), lo, fromBelow = true), s"$param occurs below $lo") - assert(!occursIn(Set(param), hi, fromBelow = false), s"$param occurs above $hi") - case _ => - } - } - - def updated(param: PolyParam, tpe: Type)(implicit ctx: Context): This = { - val newEntries = myMap(param.binder).clone - newEntries(param.paramNum) = tpe - updateEntries(param.binder, newEntries) - } - - def order(param: PolyParam, bound: PolyParam, inOrder: Boolean)(implicit ctx: Context): This = - if (related(param, bound, inOrder)) this - else { - val oldBounds = bounds(param) - val newBounds = - if (inOrder) TypeBounds(oldBounds.lo, AndType(oldBounds.hi, bound)) - else TypeBounds(OrType(oldBounds.lo, bound), oldBounds.hi) - updated(param, newBounds) - } - - /** A new constraint with all entries coming from `pt` removed. */ - def remove(pt: PolyType)(implicit ctx: Context): This = - new NaiveConstraint(myMap remove pt) - - def isRemovable(pt: PolyType, removedParam: Int = -1): Boolean = { - val entries = myMap(pt) - var noneLeft = true - var i = paramCount(entries) - while (noneLeft && i > 0) { - i -= 1 - if (i != removedParam && isBounds(entries(i))) noneLeft = false - else typeVar(entries, i) match { - case tv: TypeVar => - if (!tv.inst.exists) noneLeft = false // need to keep line around to compute instType - case _ => - } - } - noneLeft - } - - /** Drop parameter `PolyParam(poly, n)` from `bounds`, - * replacing with Nothing in the lower bound and by `Any` in the upper bound. - */ - private def dropParamIn(bounds: TypeBounds, poly: PolyType, n: Int)(implicit ctx: Context): TypeBounds = { - def drop(tp: Type): Type = tp match { - case tp: AndOrType => - val tp1 = drop(tp.tp1) - val tp2 = drop(tp.tp2) - if (!tp1.exists) tp2 - else if (!tp2.exists) tp1 - else tp - case PolyParam(`poly`, `n`) => NoType - case _ => tp - } - def approx(tp: Type, limit: Type): Type = { - val tp1 = drop(tp) - if (tp1.exists || !tp.exists) tp1 else limit - } - bounds.derivedTypeBounds( - approx(bounds.lo, defn.NothingType), approx(bounds.hi, defn.AnyType)) - } - - /** A new constraint which is derived from this constraint by removing - * the type parameter `param` from the domain and replacing all occurrences - * of the parameter elsewhere in the constraint by type `tp`. - */ - private def uncheckedReplace(param: PolyParam, tp: Type)(implicit ctx: Context): NaiveConstraint = { - - def subst(poly: PolyType, entries: Array[Type]) = { - var result = entries - var i = 0 - while (i < paramCount(entries)) { - entries(i) match { - case oldBounds: TypeBounds => - val newBounds = oldBounds.substParam(param, tp).asInstanceOf[TypeBounds] - if (oldBounds ne newBounds) { - if (result eq entries) result = entries.clone - result(i) = dropParamIn(newBounds, poly, i) - } - case _ => - } - i += 1 - } - result - } - - val pt = param.binder - val constr1 = if (isRemovable(pt, param.paramNum)) remove(pt) else updated(param, tp) - val result = new NaiveConstraint(constr1.myMap mapValues subst) - if (Config.checkConstraintsNonCyclic) result.checkNonCyclic() - result - } - - def replace(param: PolyParam, tp: Type)(implicit ctx: Context): This = { - val tp1 = tp.dealias.stripTypeVar - if (param == tp1) this else uncheckedReplace(param, tp1) - } - - def unify(p1: PolyParam, p2: PolyParam)(implicit ctx: Context): This = { - val p1Bounds = - dropParamIn(bounds(p1), p2.binder, p2.paramNum) & - dropParamIn(bounds(p2), p1.binder, p1.paramNum) - this.updated(p1, p1Bounds).updated(p2, p1) - } - - def add(poly: PolyType, tvars: List[TypeVar])(implicit ctx: Context): This = { - val nparams = poly.paramNames.length - val entries = new Array[Type](nparams * 2) - poly.paramBounds.copyToArray(entries, 0) - tvars.copyToArray(entries, nparams) - updateEntries(poly, entries) - } - - def domainPolys: List[PolyType] = myMap.keys - - def domainParams: List[PolyParam] = - for { - (poly, entries) <- myMap.toList - n <- 0 until paramCount(entries) - if isBounds(entries(n)) - } yield PolyParam(poly, n) - - def forallParams(p: PolyParam => Boolean): Boolean = { - myMap.foreachBinding { (poly, entries) => - for (i <- 0 until paramCount(entries)) - if (isBounds(entries(i)) && !p(PolyParam(poly, i))) return false - } - true - } - - def foreachTypeVar(op: TypeVar => Unit): Unit = - myMap.foreachBinding { (poly, entries) => - for (i <- 0 until paramCount(entries)) { - typeVar(entries, i) match { - case tv: TypeVar if !tv.inst.exists => op(tv) - case _ => - } - } - } - - private var myUninstVars: mutable.ArrayBuffer[TypeVar] = null - - /** The uninstantiated typevars of this constraint */ - def uninstVars: collection.Seq[TypeVar] = { - if (myUninstVars == null) { - myUninstVars = new mutable.ArrayBuffer[TypeVar] - myMap.foreachBinding { (poly, entries) => - for (i <- 0 until paramCount(entries)) { - typeVar(entries, i) match { - case tv: TypeVar if isBounds(entries(i)) => myUninstVars += tv - case _ => - } - } - } - } - myUninstVars - } - - def constrainedTypesText(printer: Printer): Text = - Text(domainPolys map (_.toText(printer)), ", ") - - def constraintText(indent: Int, printer: Printer): Text = { - val assocs = - for (param <- domainParams) - yield (" " * indent) ~ param.toText(printer) ~ entry(param).toText(printer) - Text(assocs, "\n") - } - - override def toText(printer: Printer): Text = { - val header: Text = "Constraint(" - val uninstVarsText = " uninstVars = " ~ - Text(uninstVars map (_.toText(printer)), ", ") ~ ";" - val constrainedText = - " constrained types = " ~ constrainedTypesText(printer) ~ ";" - val constraintsText = - " constraint = " ~ constraintText(3, printer) ~ ")" - Text.lines(List(header, uninstVarsText, constrainedText, constraintsText)) - } -} diff --git a/src/dotty/tools/dotc/core/TrackingConstraint.scala b/src/dotty/tools/dotc/core/TrackingConstraint.scala index 6e9ef5a2ba8d..18e37a73ed1d 100644 --- a/src/dotty/tools/dotc/core/TrackingConstraint.scala +++ b/src/dotty/tools/dotc/core/TrackingConstraint.scala @@ -93,7 +93,7 @@ class TrackingConstraint(private val myMap: ParamInfo, private def typeVar(entries: Array[Type], n: Int): Type = entries(paramCount(entries) + n) - private def entry(param: PolyParam): Type = { + def entry(param: PolyParam): Type = { val entries = myMap(param.binder) if (entries == null) NoType else entries(param.paramNum) @@ -119,55 +119,79 @@ class TrackingConstraint(private val myMap: ParamInfo, // ---------- Dependency handling ---------------------------------------------- - private def upperBits(i: Int): BitSet = less(i) + private def upperBits(less: Array[BitSet], i: Int): BitSet = less(i) - private def lowerBits(i: Int): BitSet = - (BitSet() /: less.indices) ((bits, j) => if (less(i)(j)) bits + j else bits) + private def lowerBits(less: Array[BitSet], i: Int): BitSet = + (BitSet() /: less.indices) ((bits, j) => if (less(j)(i)) bits + j else bits) - private def minUpperBits(i: Int): BitSet = { - val all = upperBits(i) + private def minUpperBits(less: Array[BitSet], i: Int): BitSet = { + val all = upperBits(less, i) all.filterNot(j => all.exists(k => less(k)(j))) } - private def minLowerBits(i: Int): BitSet = { - val all = lowerBits(i) + private def minLowerBits(less: Array[BitSet], i: Int): BitSet = { + val all = lowerBits(less, i) all.filterNot(j => all.exists(k => less(j)(k))) } - private def overParams(op: Int => BitSet): PolyParam => List[PolyParam] = param => - op(paramIndex(param)).toList.map(params).filter(contains) + private def overParams(op: (Array[BitSet], Int) => BitSet): PolyParam => List[PolyParam] = param => + op(less, paramIndex(param)).toList.map(params).filter(contains) - val upper = overParams(upperBits) - val lower = overParams(lowerBits) + val allUpper = overParams(upperBits) + val allLower = overParams(lowerBits) val minUpper = overParams(minUpperBits) val minLower = overParams(minLowerBits) + def upper(param: PolyParam): List[PolyParam] = allUpper(param) + def lower(param: PolyParam): List[PolyParam] = allLower(param) + + def exclusiveLower(param: PolyParam, butNot: PolyParam): List[PolyParam] = { + val excluded = lowerBits(less, paramIndex(butNot)) + overParams(lowerBits(_, _) &~ excluded)(param) + } + + def exclusiveUpper(param: PolyParam, butNot: PolyParam): List[PolyParam] = { + val excluded = upperBits(less, paramIndex(butNot)) + overParams(upperBits(_, _) &~ excluded)(param) + } // ---------- Info related to PolyParams ------------------------------------------- - def related(param1: PolyParam, param2: PolyParam, firstIsLower: Boolean)(implicit ctx: Context): Boolean = { - val i1 = paramIndex(param1) - val i2 = paramIndex(param2) - if (firstIsLower) less(i1)(i2) else less(i2)(i1) - } + def isLess(param1: PolyParam, param2: PolyParam)(implicit ctx: Context): Boolean = + less(paramIndex(param1))(paramIndex(param2)) - def nonParamBounds(param: PolyParam)(implicit ctx: Context): TypeBounds = + def nonParamBounds(param: PolyParam): TypeBounds = entry(param).asInstanceOf[TypeBounds] - def bounds(param: PolyParam)(implicit ctx: Context): TypeBounds = { - val bounds @ TypeBounds(lo, hi) = nonParamBounds(param) - bounds.derivedTypeBounds( - (lo /: minLower(param))(OrType.apply), - (hi /: minUpper(param))(AndType.apply)) - } - - def at(param: PolyParam)(implicit ctx: Context): Type = { - entry(param) match { - case _: TypeBounds => bounds(param) - case e => e + def checkBound(param: PolyParam, bound: Type)(implicit ctx: Context): Type = { + assert(param != bound) + bound match { + case TypeBounds(lo, hi) => + checkBound(param, lo) + checkBound(param, hi) + case bound: TypeVar => + checkBound(param, bound.underlying) + case bound: RefinedType => + checkBound(param, bound.underlying) + case bound: AndOrType => + checkBound(param, bound.tp1) + checkBound(param, bound.tp2) + case _ => } + bound } - + + def fullLowerBound(param: PolyParam)(implicit ctx: Context): Type = { + val lo = checkBound(param, nonParamBounds(param).lo) + checkBound(param, (lo /: minLower(param))(_ | _)) + } + + def fullUpperBound(param: PolyParam)(implicit ctx: Context): Type = + (nonParamBounds(param).hi /: minUpper(param))(_ & _) + + def fullBounds(param: PolyParam)(implicit ctx: Context): TypeBounds = + nonParamBounds(param).derivedTypeBounds(fullLowerBound(param), fullUpperBound(param)) + def typeVarOfParam(param: PolyParam): Type = { val entries = myMap(param.binder) if (entries == null) NoType @@ -177,30 +201,19 @@ class TrackingConstraint(private val myMap: ParamInfo, } } -// ---------- Type splitting -------------------------------------------------- - - /** The set of "dependent" constrained parameters that unconditionally strengthen bound `tp`. - * @param seenFromBelow If true, `bound` is an upper bound, else a lower bound. - */ - private def depParams(tp: Type, seenFromBelow: Boolean): Set[PolyParam] = tp match { - case tp: PolyParam if contains(tp) => - Set(tp) - case tp: AndOrType if seenFromBelow == tp.isAnd => - depParams(tp.tp1, seenFromBelow) | depParams(tp.tp2, seenFromBelow) - case _ => - Set.empty - } +// ---------- Adding PolyTypes -------------------------------------------------- - /** The bound type `tp` without dependent parameters. + /** The bound type `tp` without dependent parameters * NoType if type consists only of dependent parameters. * @param seenFromBelow If true, `bound` is an upper bound, else a lower bound. */ - private def stripParams(tp: Type, seenFromBelow: Boolean)(implicit ctx: Context): Type = tp match { - case tp: PolyParam if contains(tp) => - NoType + private def stripParams(tp: Type, handleParam: (PolyParam, Boolean) => Type, + seenFromBelow: Boolean)(implicit ctx: Context): Type = tp match { + case tp: PolyParam => + handleParam(tp, seenFromBelow) case tp: AndOrType if seenFromBelow == tp.isAnd => - val tp1 = nonParamType(tp.tp1, seenFromBelow) - val tp2 = nonParamType(tp.tp2, seenFromBelow) + val tp1 = stripParams(tp.tp1, handleParam, seenFromBelow) + val tp2 = stripParams(tp.tp2, handleParam, seenFromBelow) if (tp1.exists) if (tp2.exists) tp.derivedAndOrType(tp1, tp2) else tp1 @@ -213,95 +226,94 @@ class TrackingConstraint(private val myMap: ParamInfo, * A top or bottom type if type consists only of dependent parameters. * @param seenFromBelow If true, `bound` is an upper bound, else a lower bound. */ - private def nonParamType(tp: Type, seenFromBelow: Boolean)(implicit ctx: Context): Type = - stripParams(tp, seenFromBelow).orElse(if (seenFromBelow) defn.AnyType else defn.NothingType) + private def nonParamType(tp: Type, handleParam: (PolyParam, Boolean) => Type, + seenFromBelow: Boolean)(implicit ctx: Context): Type = + stripParams(tp, handleParam, seenFromBelow) + .orElse(if (seenFromBelow) defn.AnyType else defn.NothingType) - /** The `tp1 is a TypeBounds type, the bounds without dependent parameters, - * otherwise `tp`. + /** The bounds of `tp1` without dependent parameters. + * @pre `tp` is a TypeBounds type. */ - private def nonParamType(tp: Type)(implicit ctx: Context): Type = tp match { + private def nonParamBounds(tp: Type, handleParam: (PolyParam, Boolean) => Type)(implicit ctx: Context): Type = tp match { case tp @ TypeBounds(lo, hi) => tp.derivedTypeBounds( - nonParamType(lo, seenFromBelow = false), - nonParamType(hi, seenFromBelow = true)) - case _ => - tp + nonParamType(lo, handleParam, seenFromBelow = false), + nonParamType(hi, handleParam, seenFromBelow = true)) } - /** An updated partial order matrix that incorporates `less` and also reflects the new `bounds` - * for parameter `param`. - */ - private def updatedLess(less: Array[BitSet], param: PolyParam, bounds: Type): Array[BitSet] = bounds match { - case TypeBounds(lo, hi) => - updatedLess( - updatedLess(less, param, lo, seenFromBelow = false), - param, hi, seenFromBelow = true) - case _ => - less + def add(poly: PolyType, tvars: List[TypeVar])(implicit ctx: Context): This = { + assert(!contains(poly)) + val nparams = poly.paramNames.length + val entries1 = new Array[Type](nparams * 2) + poly.paramBounds.copyToArray(entries1, 0) + tvars.copyToArray(entries1, nparams) + val is = poly.paramBounds.indices + val newParams = is.map(PolyParam(poly, _)) + val params1 = params ++ newParams + var less1 = less ++ is.map(Function.const(BitSet.empty)) + for (i <- is) { + def handleParam(param: PolyParam, seenFromBelow: Boolean): Type = { + def record(paramIdx: Int): Type = { + less1 = + if (seenFromBelow) updatedLess(less1, nparams + i, paramIdx) + else updatedLess(less1, paramIdx, nparams + i) + NoType + } + if (param.binder eq poly) record(nparams + param.paramNum) + else if (contains(param.binder)) record(paramIndex(param)) + else param + } + entries1(i) = checkBound(newParams(i), nonParamBounds(entries1(i), handleParam)) + } + newConstraint(myMap.updated(poly, entries1), less1, params1) } - /** An updated partial order matrix that incorporates `less` and also reflects that `param` has a new - * `bound`, where `seenFromBelow` is true iff `bound` is an upper bound for `param`. - */ - def updatedLess(less: Array[BitSet], param: PolyParam, bound: Type, seenFromBelow: Boolean): Array[BitSet] = - updatedLess(less, param, depParams(bound, seenFromBelow).iterator, inOrder = seenFromBelow) - - /** An updated partial order matrix that incorporates `less` and also reflects that `param` relates - * to all parameters in `ps2` wrt <:< if `inOrder` is true, `>:>` otherwise. - */ - def updatedLess(less: Array[BitSet], p1: PolyParam, ps2: Iterator[PolyParam], inOrder: Boolean): Array[BitSet] = - if (ps2.hasNext) updatedLess(updatedLess(less, p1, ps2.next, inOrder), p1, ps2, inOrder) - else less +// ---------- Updates ------------------------------------------------------------ /** An updated partial order matrix that incorporates `less` and also reflects that `param` relates * to `p2` wrt <:< if `inOrder` is true, `>:>` otherwise. */ - def updatedLess(less: Array[BitSet], p1: PolyParam, p2: PolyParam, inOrder: Boolean): Array[BitSet] = - if (!inOrder) updatedLess(less, p2, p1, true) - else { - val i1 = paramIndex(p1) - val i2 = paramIndex(p2) + private def updatedLess(less: Array[BitSet], i1: Int, i2: Int): Array[BitSet] = { if (i1 == i2 || less(i1)(i2)) less else { val result = less.clone - result(i1) = result(i1) + i2 | upperBits(i2) - assert(!result(i1)(i1)) - for (j <- lowerBits(i1)) { - result(j) = result(j) + i2 | upperBits(i2) + val newUpper = upperBits(less, i2) + i2 + def update(j: Int) = { + result(j) |= newUpper assert(!result(j)(j)) } + update(i1) + lowerBits(less, i1).foreach(update) result } - } + } -// ---------- Updates ------------------------------------------------------------ - - def order(param: PolyParam, bound: PolyParam, inOrder: Boolean)(implicit ctx: Context): This = { - val less1 = updatedLess(less, param, bound, inOrder) + def addLess(p1: PolyParam, p2: PolyParam)(implicit ctx: Context): This = { + val less1 = updatedLess(less, paramIndex(p1), paramIndex(p2)) if (less1 eq less) this else newConstraint(myMap, less1, params) } - def nonParamUpdated(param: PolyParam, tpe: Type)(implicit ctx: Context): This = { - val entries1 = myMap(param.binder).clone - entries1(param.paramNum) = tpe - newConstraint(myMap.updated(param.binder, entries1), less, params) + def updateEntry(param: PolyParam, tp: Type)(implicit ctx: Context): This = { + val entries = myMap(param.binder) + val entry = entries(param.paramNum) + if (entry eq tp) this + else { + if (!tp.isInstanceOf[TypeBounds]) typr.println(i"inst entry $param to $tp") + val entries1 = entries.clone + entries1(param.paramNum) = checkBound(param, tp) + newConstraint(myMap.updated(param.binder, entries1), less, params) + } } - def updated(param: PolyParam, tpe: Type)(implicit ctx: Context): This = { - val less1 = updatedLess(less, param, tpe) - val entries = myMap(param.binder) - val entry1 = nonParamType(tpe) - val idx = param.paramNum - val entries1 = - if (entry1 eq entries(idx)) entries - else { - val entries1 = entries.clone - entries1(idx) = entry1 - entries1 - } - newConstraint(myMap.updated(param.binder, entries1), less1, params) + def unify(p1: PolyParam, p2: PolyParam)(implicit ctx: Context): This = { + val p1Bounds = + dropParamIn(nonParamBounds(p1), p2.binder, p2.paramNum) & + dropParamIn(nonParamBounds(p2), p1.binder, p1.paramNum) + this.updateEntry(p1, p1Bounds).updateEntry(p2, p1) } - + +// ---------- Removals ------------------------------------------------------------ + /** Drop parameter `PolyParam(poly, n)` from `bounds`, * replacing with Nothing in the lower bound and by `Any` in the upper bound. */ @@ -324,10 +336,6 @@ class TrackingConstraint(private val myMap: ParamInfo, approx(bounds.lo, defn.NothingType), approx(bounds.hi, defn.AnyType)) } - /** A new constraint which is derived from this constraint by removing - * the type parameter `param` from the domain and replacing all occurrences - * of the parameter elsewhere in the constraint by type `tp`. - */ def replace(param: PolyParam, tp: Type)(implicit ctx: Context): TrackingConstraint = { val replacement = tp.dealias.stripTypeVar @@ -340,7 +348,7 @@ class TrackingConstraint(private val myMap: ParamInfo, val newBounds = oldBounds.substParam(param, replacement).asInstanceOf[TypeBounds] if (oldBounds ne newBounds) { if (result eq entries) result = entries.clone - result(i) = dropParamIn(newBounds, poly, i) + result(i) = checkBound(PolyParam(poly, i), dropParamIn(newBounds, poly, i)) } case _ => } @@ -351,39 +359,13 @@ class TrackingConstraint(private val myMap: ParamInfo, if (param == replacement) this else { + assert(replacement.isValueType) val pt = param.binder - val constr1 = if (isRemovable(pt, param.paramNum)) remove(pt) else updated(param, replacement) - val result = new TrackingConstraint(constr1.myMap mapValues subst, constr1.less, constr1.params) - if (Config.checkConstraintsNonCyclic) result.checkNonCyclic() - result + val constr1 = if (isRemovable(pt, param.paramNum)) remove(pt) else updateEntry(param, replacement) + newConstraint(constr1.myMap mapValues subst, constr1.less, constr1.params) } } - def unify(p1: PolyParam, p2: PolyParam)(implicit ctx: Context): This = { - val p1Bounds = - dropParamIn(nonParamBounds(p1), p2.binder, p2.paramNum) & - dropParamIn(nonParamBounds(p2), p1.binder, p1.paramNum) - this.nonParamUpdated(p1, p1Bounds).nonParamUpdated(p2, p1) - } - - def add(poly: PolyType, tvars: List[TypeVar])(implicit ctx: Context): This = { - assert(!contains(poly)) - val nparams = poly.paramNames.length - val entries1 = new Array[Type](nparams * 2) - poly.paramBounds.copyToArray(entries1, 0) - tvars.copyToArray(entries1, nparams) - val is = poly.paramBounds.indices - val newParams = is.map(PolyParam(poly, _)) - val params1 = params ++ newParams - var less1 = less ++ is.map(Function.const(BitSet.empty)) - for (i <- is) { - less1 = updatedLess(less1, newParams(i), entries1(i)) - entries1(i) = nonParamType(entries1(i)) - } - newConstraint(myMap.updated(poly, entries1), less1, params1) - } - - /** A new constraint with all entries coming from `pt` removed. */ def remove(pt: PolyType)(implicit ctx: Context): This = { val start = polyStart(pt) val skipped = pt.paramNames.length @@ -425,7 +407,7 @@ class TrackingConstraint(private val myMap: ParamInfo, def domainPolys: List[PolyType] = polyTypes.toList - def domainParams: List[PolyParam] = params.toList + def domainParams: List[PolyParam] = params.toList.filter(contains) def forallParams(p: PolyParam => Boolean): Boolean = { myMap.foreachBinding { (poly, entries) => @@ -454,7 +436,7 @@ class TrackingConstraint(private val myMap: ParamInfo, myMap.foreachBinding { (poly, entries) => for (i <- 0 until paramCount(entries)) { typeVar(entries, i) match { - case tv: TypeVar if isBounds(entries(i)) => myUninstVars += tv + case tv: TypeVar if !tv.inst.exists && isBounds(entries(i)) => myUninstVars += tv case _ => } } @@ -468,13 +450,8 @@ class TrackingConstraint(private val myMap: ParamInfo, private def checkNonCyclic(idx: Int)(implicit ctx: Context): Unit = assert(!less(idx)(idx), i"cyclic constraint involving ${params(idx)}") - def checkNonCyclic(pt: PolyType, entries: Array[Type])(implicit ctx: Context): Unit = - for (i <- entries.indices) checkNonCyclic(paramIndex(PolyParam(pt, i))) - def checkNonCyclic()(implicit ctx: Context): Unit = for (i <- params.indices) checkNonCyclic(i) - - def checkNonCyclicTrans()(implicit ctx: Context): Unit = checkNonCyclic() // ---------- toText ----------------------------------------------------- diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index 0d2dc20d3e46..046424eaf95b 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -85,7 +85,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi assert(isSatisfiable, constraint.show) } - def isSubType(tp1: Type, tp2: Type): Boolean = ctx.traceIndented(s"isSubType ${traceInfo(tp1, tp2)} ${if (Config.verboseExplainSubtype) s" ${tp1.getClass}, ${tp2.getClass}" else ""}", subtyping) /*<|<*/ { + 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 { @@ -1118,26 +1118,6 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi false } - /** Constraint `c1` subsumes constraint `c2`, if under `c2` as constraint we have - * for all poly params `p` defined in `c2` as `p >: L2 <: U2`: - * - * c1 defines p with bounds p >: L1 <: U1, and - * L2 <: L1, and - * U1 <: U2 - * - * Both `c1` and `c2` are required to derive from constraint `pre`, possibly - * narrowing it with further bounds. - */ - def subsumes(c1: Constraint, c2: Constraint, pre: Constraint): Boolean = - if (c2 eq pre) true - else if (c1 eq pre) false - else { - val saved = constraint - try - c2.forallParams(p => c1.contains(p) && isSubType(c1.bounds(p), c2.bounds(p))) - finally constraint = saved - } - /** A new type comparer of the same type as this one, using the given context. */ def copyIn(ctx: Context) = new TypeComparer(ctx) @@ -1147,8 +1127,14 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi 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) s" ${tp1.getClass} ${tp2.getClass}${if (frozenConstraint) " frozen" else ""}" else "") + 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) = { diff --git a/src/dotty/tools/dotc/core/TyperState.scala b/src/dotty/tools/dotc/core/TyperState.scala index cb8538e26a1a..bdba128a93a9 100644 --- a/src/dotty/tools/dotc/core/TyperState.scala +++ b/src/dotty/tools/dotc/core/TyperState.scala @@ -17,7 +17,7 @@ class TyperState(r: Reporter) extends DotClass with Showable { def reporter = r /** The current constraint set */ - def constraint: Constraint = new NaiveConstraint(SimpleMap.Empty)//new TrackingConstraint(SimpleMap.Empty, Array(), Array()) + def constraint: Constraint = new TrackingConstraint(SimpleMap.Empty, Array(), Array()) def constraint_=(c: Constraint): Unit = {} /** The uninstantiated variables */ @@ -38,7 +38,7 @@ class TyperState(r: Reporter) extends DotClass with Showable { * is done only in a temporary way for contexts that may be retracted * without also retracting the type var as a whole. */ - def instType(tvar: TypeVar)(implicit ctx: Context): Type = constraint.at(tvar.origin) match { + def instType(tvar: TypeVar)(implicit ctx: Context): Type = constraint.entry(tvar.origin) match { case _: TypeBounds => NoType case tp: PolyParam => var tvar1 = constraint.typeVarOfParam(tp) diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 8249e5a4c37f..30e394941b2c 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -2251,7 +2251,7 @@ object Types { * is also a singleton type. */ def instantiate(fromBelow: Boolean)(implicit ctx: Context): Type = { - def upperBound = ctx.typerState.constraint.bounds(origin).hi + def upperBound = ctx.typerState.constraint.fullUpperBound(origin) def isSingleton(tp: Type): Boolean = tp match { case tp: SingletonType => true case AndType(tp1, tp2) => isSingleton(tp1) | isSingleton(tp2) diff --git a/src/dotty/tools/dotc/printing/PlainPrinter.scala b/src/dotty/tools/dotc/printing/PlainPrinter.scala index e86539d6cfde..1a6cc77b2991 100644 --- a/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -145,10 +145,10 @@ class PlainPrinter(_ctx: Context) extends Printer { if (tp.isInstantiated) toTextLocal(tp.instanceOpt) ~ "'" // debug for now, so that we can see where the TypeVars are. else { - val bounds = ctx.typerState.constraint.at(tp.origin) match { - case bounds: TypeBounds => bounds - case _ => TypeBounds.empty - } + val constr = ctx.typerState.constraint + val bounds = + if (constr.contains(tp)) constr.fullBounds(tp.origin) + else TypeBounds.empty "(" ~ toText(tp.origin) ~ "?" ~ toText(bounds) ~ ")" } case _ => diff --git a/src/dotty/tools/dotc/typer/Inferencing.scala b/src/dotty/tools/dotc/typer/Inferencing.scala index 05be46f29145..e41b2d194274 100644 --- a/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/src/dotty/tools/dotc/typer/Inferencing.scala @@ -203,7 +203,7 @@ trait Inferencing { this: Checking => if (v == 1) tvar.instantiate(fromBelow = false) else if (v == -1) tvar.instantiate(fromBelow = true) else { - val bounds = ctx.typerState.constraint.bounds(tvar.origin) + val bounds = ctx.typerState.constraint.fullBounds(tvar.origin) if (!(bounds.hi <:< bounds.lo)) result = Some(tvar) tvar.instantiate(fromBelow = false) } diff --git a/src/dotty/tools/dotc/typer/ProtoTypes.scala b/src/dotty/tools/dotc/typer/ProtoTypes.scala index e16b83afd67e..521f0deaae0e 100644 --- a/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -325,6 +325,7 @@ object ProtoTypes { else pt val tvars = if (owningTree.isEmpty) Nil else newTypeVars(added) state.constraint = state.constraint.add(added, tvars) + ctx.typeComparer.initialize(added) (added, tvars) } @@ -376,7 +377,7 @@ object ProtoTypes { case tp: TypeAlias => // default case, inlined for speed tp.derivedTypeAlias(wildApprox(tp.alias, theMap)) case tp @ PolyParam(poly, pnum) => // !!! todo adapt to TrackingConstraint - ctx.typerState.constraint.at(tp) match { + ctx.typerState.constraint.entry(tp) match { case bounds: TypeBounds => wildApprox(WildcardType(bounds)) case NoType => WildcardType(wildApprox(poly.paramBounds(pnum)).bounds) case inst => wildApprox(inst) From 179a1bbd2bb40d1c40823d1d3a1f30f8ecdf5c56 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 18 Jan 2015 18:43:59 +0100 Subject: [PATCH 48/72] Removed check from addConstraint addConstraint contained a special case where a situation like P { ... } <: P was short-pathed to `return true`. But the same was not done if the constraint was added indirectly by propagation. It's not clear whether a special treatement of this is needed for correctness. If it is needed, then it would be needed eberywhere. So wince we do not want to implement it everywhere wihtout proof of necessity, it is better to fail fast and drop the special treatment entirely. --- src/dotty/tools/dotc/core/ConstraintHandling.scala | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/dotty/tools/dotc/core/ConstraintHandling.scala b/src/dotty/tools/dotc/core/ConstraintHandling.scala index 530bbfce4baa..2e771faf23d7 100644 --- a/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -245,24 +245,12 @@ trait ConstraintHandling { case bound: ErrorType => true case _ => - if (occursAtToplevel(param, bound)) fromBelow - else if (fromBelow) addLowerBound(param, bound) + if (fromBelow) addLowerBound(param, bound) else addUpperBound(param, bound) } finally addConstraintInvocations -= 1 } } - - private def occursAtToplevel(param: Type, tp: Type): Boolean = tp match { - case tp: PolyParam => - param == tp - case bound: TypeProxy => - occursAtToplevel(param, bound.underlying) - case bound: AndOrType => - occursAtToplevel(param, bound.tp1) || occursAtToplevel(param, bound.tp2) - case _ => - false - } def checkPropagated(msg: => String)(result: Boolean): Boolean = { if (result && addConstraintInvocations == 0) { From 952aa7f8d17579d3ee1b20ea177206e570de497f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 18 Jan 2015 18:45:03 +0100 Subject: [PATCH 49/72] Avoid adding constraint under typeVarsMissContext is true SubType check is true anyway, no need to spend work. Also this allows a more consistent treatment of bounded wildcard types in addConstraint and isSubType. In both cases we now compare with the outer bound of the wildcard type. Previously addConstraint treated boundes wildcard types like unbounded ones, which is not consistent. --- .../tools/dotc/core/ConstraintHandling.scala | 5 +++- src/dotty/tools/dotc/core/TypeComparer.scala | 28 ++++++++++--------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/dotty/tools/dotc/core/ConstraintHandling.scala b/src/dotty/tools/dotc/core/ConstraintHandling.scala index 2e771faf23d7..ff7ffdfb16ae 100644 --- a/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -241,7 +241,10 @@ trait ConstraintHandling { case bound: AndOrType if fromBelow != bound.isAnd => addConstraint(param, bound.tp1, fromBelow) && addConstraint(param, bound.tp2, fromBelow) case bound: WildcardType => - true + bound.optBounds match { + case TypeBounds(lo, hi) => addConstraint(param, if (fromBelow) lo else hi, fromBelow) + case NoType => true + } case bound: ErrorType => true case _ => diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index 046424eaf95b..faeef559ce3a 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -183,9 +183,10 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi tp2 == tp1 || { if (solvedConstraint && (constraint contains tp2)) isSubType(tp1, bounds(tp2).lo) else + (ctx.mode is Mode.TypevarsMissContext) || constraintImpliesSuper(tp2, tp1) || { if (canConstrain(tp2)) addConstraint(tp2, tp1.widenExpr, fromBelow = true) - else (ctx.mode is Mode.TypevarsMissContext) || secondTry(tp1, tp2) + else secondTry(tp1, tp2) } } comparePolyParam @@ -241,23 +242,24 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi case OrType(tp11, tp12) => isSubType(tp11, tp2) && isSubType(tp12, 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.flagInstantiationToNothing) assert(false, msg) + else ctx.log(msg) + } + true + } def comparePolyParam = tp1 == tp2 || { if (solvedConstraint && (constraint contains tp1)) isSubType(bounds(tp1).lo, tp2) else + (ctx.mode is Mode.TypevarsMissContext) || constraintImpliesSub(tp1, tp2) || { - if (canConstrain(tp1)) - addConstraint(tp1, tp2, fromBelow = false) && { - if ((!frozenConstraint) && - (tp2 isRef defn.NothingClass) && - state.isGlobalCommittable) { - def msg = s"!!! instantiated to Nothing: $tp1, constraint = ${constraint.show}" - if (Config.flagInstantiationToNothing) assert(false, msg) - else ctx.log(msg) - } - true - } - else (ctx.mode is Mode.TypevarsMissContext) || thirdTry(tp1, tp2) + if (canConstrain(tp1)) addConstraint(tp1, tp2, fromBelow = false) && flagNothingBound + else thirdTry(tp1, tp2) } } comparePolyParam From 23ae8d935ccac3a814140534d4c9df76e0b3d345 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 18 Jan 2015 18:50:55 +0100 Subject: [PATCH 50/72] Dropped non-sensical transform in unify dropParamsIn extrapolates parameters with Nothing/Any. This has nothing to do with unification! The dropParamsIn transform could not be eleiminated previously because it caused faulures. Nice to see that it works now! --- src/dotty/tools/dotc/core/TrackingConstraint.scala | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/dotty/tools/dotc/core/TrackingConstraint.scala b/src/dotty/tools/dotc/core/TrackingConstraint.scala index 18e37a73ed1d..34b97c99ece7 100644 --- a/src/dotty/tools/dotc/core/TrackingConstraint.scala +++ b/src/dotty/tools/dotc/core/TrackingConstraint.scala @@ -306,9 +306,7 @@ class TrackingConstraint(private val myMap: ParamInfo, } def unify(p1: PolyParam, p2: PolyParam)(implicit ctx: Context): This = { - val p1Bounds = - dropParamIn(nonParamBounds(p1), p2.binder, p2.paramNum) & - dropParamIn(nonParamBounds(p2), p1.binder, p1.paramNum) + val p1Bounds = (nonParamBounds(p1) & nonParamBounds(p2)).substParam(p2, p1) this.updateEntry(p1, p1Bounds).updateEntry(p2, p1) } From 14f4c964953535cb7375343ec5e8575e37083aba Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 18 Jan 2015 18:56:38 +0100 Subject: [PATCH 51/72] Removed checkBound tests They were too sweeping, served essentially as smkoke-screens for debugging until now. Example: checkBound(P, P | T) would fail even though P | T is a legal upper bound for P. (It's an illegal lower bound). --- .../tools/dotc/core/TrackingConstraint.scala | 31 +++---------------- 1 file changed, 5 insertions(+), 26 deletions(-) diff --git a/src/dotty/tools/dotc/core/TrackingConstraint.scala b/src/dotty/tools/dotc/core/TrackingConstraint.scala index 34b97c99ece7..5a77b88fcd0d 100644 --- a/src/dotty/tools/dotc/core/TrackingConstraint.scala +++ b/src/dotty/tools/dotc/core/TrackingConstraint.scala @@ -163,28 +163,8 @@ class TrackingConstraint(private val myMap: ParamInfo, def nonParamBounds(param: PolyParam): TypeBounds = entry(param).asInstanceOf[TypeBounds] - def checkBound(param: PolyParam, bound: Type)(implicit ctx: Context): Type = { - assert(param != bound) - bound match { - case TypeBounds(lo, hi) => - checkBound(param, lo) - checkBound(param, hi) - case bound: TypeVar => - checkBound(param, bound.underlying) - case bound: RefinedType => - checkBound(param, bound.underlying) - case bound: AndOrType => - checkBound(param, bound.tp1) - checkBound(param, bound.tp2) - case _ => - } - bound - } - - def fullLowerBound(param: PolyParam)(implicit ctx: Context): Type = { - val lo = checkBound(param, nonParamBounds(param).lo) - checkBound(param, (lo /: minLower(param))(_ | _)) - } + def fullLowerBound(param: PolyParam)(implicit ctx: Context): Type = + (nonParamBounds(param).lo /: minLower(param))(_ | _) def fullUpperBound(param: PolyParam)(implicit ctx: Context): Type = (nonParamBounds(param).hi /: minUpper(param))(_ & _) @@ -263,7 +243,7 @@ class TrackingConstraint(private val myMap: ParamInfo, else if (contains(param.binder)) record(paramIndex(param)) else param } - entries1(i) = checkBound(newParams(i), nonParamBounds(entries1(i), handleParam)) + entries1(i) = nonParamBounds(entries1(i), handleParam) } newConstraint(myMap.updated(poly, entries1), less1, params1) } @@ -298,9 +278,8 @@ class TrackingConstraint(private val myMap: ParamInfo, val entry = entries(param.paramNum) if (entry eq tp) this else { - if (!tp.isInstanceOf[TypeBounds]) typr.println(i"inst entry $param to $tp") val entries1 = entries.clone - entries1(param.paramNum) = checkBound(param, tp) + entries1(param.paramNum) = tp newConstraint(myMap.updated(param.binder, entries1), less, params) } } @@ -346,7 +325,7 @@ class TrackingConstraint(private val myMap: ParamInfo, val newBounds = oldBounds.substParam(param, replacement).asInstanceOf[TypeBounds] if (oldBounds ne newBounds) { if (result eq entries) result = entries.clone - result(i) = checkBound(PolyParam(poly, i), dropParamIn(newBounds, poly, i)) + result(i) = dropParamIn(newBounds, poly, i) } case _ => } From 9b260d087a4c63c39e404fd6ba7ade286fdfd8e8 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 18 Jan 2015 19:04:05 +0100 Subject: [PATCH 52/72] Moved previously failing tests to pos GADTs now work again (they stopped workign when we went to the inductive satisfiability checks). The deep for expression also works, even with some more levels added. --- tests/pending/pos/t2624.scala | 4 --- tests/pending/pos/t267.scala | 55 ----------------------------- tests/{pending => }/pos/i0268.scala | 0 tests/{pending => }/pos/t0674.scala | 13 ++++--- tests/{pending => }/pos/t1208.scala | 0 5 files changed, 6 insertions(+), 66 deletions(-) delete mode 100644 tests/pending/pos/t2624.scala delete mode 100644 tests/pending/pos/t267.scala rename tests/{pending => }/pos/i0268.scala (100%) rename tests/{pending => }/pos/t0674.scala (86%) rename tests/{pending => }/pos/t1208.scala (100%) diff --git a/tests/pending/pos/t2624.scala b/tests/pending/pos/t2624.scala deleted file mode 100644 index 76f0e303698e..000000000000 --- a/tests/pending/pos/t2624.scala +++ /dev/null @@ -1,4 +0,0 @@ -object Test { - List(1).map(identity(_)) - List(1).map(identity) // this didn't typecheck before the fix -} diff --git a/tests/pending/pos/t267.scala b/tests/pending/pos/t267.scala deleted file mode 100644 index 7e5876eae99b..000000000000 --- a/tests/pending/pos/t267.scala +++ /dev/null @@ -1,55 +0,0 @@ -package expAbstractData - -/** A base class consisting of - * - a root trait (i.e. abstract class) `Exp' with an `eval' function - * - an abstract type `exp' bounded by `Exp' - * - a concrete instance class `Num' of `Exp' for numeric literals - */ -trait Base { - type exp <: Exp - - trait Exp { - def eval: Int - } - class Num(v: Int) extends Exp { self: exp => - val value = v - def eval = value - } -} - -object testBase extends App with Base { - type exp = Exp - val term = new Num(2); - Console.println(term.eval) -} - -/** Data extension: An extension of `Base' with `Plus' expressions - */ -trait BasePlus extends Base { - class Plus(l: exp, r: exp) extends Exp { self: exp => - val left = l - val right = r - def eval = left.eval + right.eval - } -} - -/** Operation extension: An extension of `Base' with 'show' methods. - */ -trait Show extends Base { - type exp <: Exp1 - - trait Exp1 extends Exp { - def show: String - } - class Num1(v: Int) extends Num(v) with Exp1 { self: exp with Num1 => - def show = value.toString() - } -} - -/** Operation extension: An extension of `BasePlus' with 'show' methods. - */ -trait ShowPlus extends BasePlus with Show { - class Plus1(l: exp, r: exp) extends Plus(l, r) with Exp1 { self: exp with Plus1 => - def show = left.show + " + " + right.show - } -} diff --git a/tests/pending/pos/i0268.scala b/tests/pos/i0268.scala similarity index 100% rename from tests/pending/pos/i0268.scala rename to tests/pos/i0268.scala diff --git a/tests/pending/pos/t0674.scala b/tests/pos/t0674.scala similarity index 86% rename from tests/pending/pos/t0674.scala rename to tests/pos/t0674.scala index e405f4b8d2e2..a734091da34e 100644 --- a/tests/pending/pos/t0674.scala +++ b/tests/pos/t0674.scala @@ -11,8 +11,7 @@ for(a <- Some(1); i <- Some(9); j <- Some(10); k <- Some(11); - l <- Some(12) -/* + l <- Some(12); m <- Some(13); n <- Some(14); o <- Some(15); @@ -40,10 +39,10 @@ for(a <- Some(1); l <- Some(12); m <- Some(13); n <- Some(14); - o <- Some(15)*/ -// p <- Some(16); -// q <- Some(17) -// r <- Some(18); -// s <- Some(19) + o <- Some(15); + p <- Some(16); + q <- Some(17); + r <- Some(18); + s <- Some(19) ) yield a) } diff --git a/tests/pending/pos/t1208.scala b/tests/pos/t1208.scala similarity index 100% rename from tests/pending/pos/t1208.scala rename to tests/pos/t1208.scala From 5354f88c6ef2dafd5ae876c16c9758155a780783 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 19 Jan 2015 09:09:23 +0100 Subject: [PATCH 53/72] Fixed soundness hole in TypeComparer Two named types with same names and NoPrefix prefixes are not necessarily equal! The fix uncovered an error in tailrec. When run on Decorators.scala, tailrec in its old position at the end of a group produces not -Ycheckable code. Problem was fixed by moving TailRec into its own group. --- src/dotty/tools/dotc/Compiler.scala | 6 ++++-- src/dotty/tools/dotc/Run.scala | 1 + src/dotty/tools/dotc/core/TypeComparer.scala | 9 +++++++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/dotty/tools/dotc/Compiler.scala b/src/dotty/tools/dotc/Compiler.scala index 943b54d7f9a8..c5c1d8713342 100644 --- a/src/dotty/tools/dotc/Compiler.scala +++ b/src/dotty/tools/dotc/Compiler.scala @@ -45,8 +45,10 @@ class Compiler { List(new RefChecks, new ElimRepeated, new ElimLocals, - new ExtensionMethods, - new TailRec), + new ExtensionMethods), + List(new TailRec), // TailRec needs to be in its own group for now. + // Otherwise it produces -Ycheck incorrect code for + // file core/Decorators.scala. List(new PatternMatcher, new ExplicitOuter, new Splitter), diff --git a/src/dotty/tools/dotc/Run.scala b/src/dotty/tools/dotc/Run.scala index f7d89e897f91..abee30aab69c 100644 --- a/src/dotty/tools/dotc/Run.scala +++ b/src/dotty/tools/dotc/Run.scala @@ -46,6 +46,7 @@ class Run(comp: Compiler)(implicit ctx: Context) { .filterNot(ctx.settings.Yskip.value.containsPhase(_)) // TODO: skip only subphase for (phase <- phasesToRun) if (!ctx.reporter.hasErrors) { + if (ctx.settings.verbose.value) println(s"[$phase]") units = phase.runOn(units) def foreachUnit(op: Context => Unit)(implicit ctx: Context): Unit = for (unit <- units) op(ctx.fresh.setPhase(phase.next).setCompilationUnit(unit)) diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index faeef559ce3a..720e372d21c3 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -167,8 +167,13 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi ) } ) - else (tp1.name eq tp2.name) && isSameType(tp1.prefix, tp2.prefix) - ) || isHKSubType || secondTryNamed(tp1, tp2) + else + (tp1.name eq tp2.name) && + isSameType(tp1.prefix, tp2.prefix) && + (tp1.signature == tp1.signature) && + !tp1.isInstanceOf[WithFixedSym] && + !tp2.isInstanceOf[WithFixedSym] + ) || isHKSubType || secondTryNamed(tp1, tp2) case tp1: ThisType if tp1.cls eq tp2.symbol.moduleClass => isSubType(tp1.cls.owner.thisType, tp2.prefix) case _ => From 6771786961e708178e56b22b1c2869712f6c3891 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 19 Jan 2015 09:51:11 +0100 Subject: [PATCH 54/72] Compare ThisType refactoring Moved comparisons between ThisType x$.this and NamedType x.type where x$ is the module class of x. They were uner NamedType, are now under ThisType. That way NamedType reasoning is a bit uncluttered. --- src/dotty/tools/dotc/core/TypeComparer.scala | 22 +++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index 720e372d21c3..ae9e0f660570 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -174,8 +174,6 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi !tp1.isInstanceOf[WithFixedSym] && !tp2.isInstanceOf[WithFixedSym] ) || isHKSubType || secondTryNamed(tp1, tp2) - case tp1: ThisType if tp1.cls eq tp2.symbol.moduleClass => - isSubType(tp1.cls.owner.thisType, tp2.prefix) case _ => isHKSubType || secondTry(tp1, tp2) } @@ -216,6 +214,8 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi // A's selftype derives from B and B's selftype derives from A. tp1.cls.classInfo.selfType.derivesFrom(tp2.cls) && tp2.cls.classInfo.selfType.derivesFrom(tp1.cls) + case tp1: TermRef if tp2.cls eq tp1.symbol.moduleClass => + isSubType(tp1.prefix, tp2.cls.owner.thisType) case _ => secondTry(tp1, tp2) } @@ -238,14 +238,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi private def secondTry(tp1: Type, tp2: Type): Boolean = tp1 match { case tp1: NamedType => - tp2 match { - case tp2: ThisType if tp2.cls eq tp1.symbol.moduleClass => - isSubType(tp1.prefix, tp2.cls.owner.thisType) - case _ => - secondTryNamed(tp1, tp2) - } - case OrType(tp11, tp12) => - isSubType(tp11, tp2) && isSubType(tp12, tp2) + secondTryNamed(tp1, tp2) case tp1: PolyParam => def flagNothingBound = { if ((!frozenConstraint) && @@ -268,6 +261,13 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi } } comparePolyParam + case tp1: ThisType => + tp2 match { + case tp2: TermRef if tp1.cls eq tp2.symbol.moduleClass => + isSubType(tp1.cls.owner.thisType, tp2.prefix) + case _ => + thirdTry(tp1, tp2) + } case tp1: SkolemType => tp2 match { case tp2: SkolemType if tp1 == tp2 => true @@ -287,6 +287,8 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi isSubType(tp1.ref, tp2) case tp1: AnnotatedType => isSubType(tp1.tpe, tp2) + case OrType(tp11, tp12) => + isSubType(tp11, tp2) && isSubType(tp12, tp2) case ErrorType => true case _ => From 50444f8a2f749cf30a7352a858141c7b987baf0d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 19 Jan 2015 11:28:56 +0100 Subject: [PATCH 55/72] Refactor handling of NamedTypes in isSubType GADT handling goes towards the back. Dealiasing is done early. Eventual aim: Avoid redoing dealising when adding constraints. --- src/dotty/tools/dotc/core/TypeComparer.scala | 118 +++++++++---------- 1 file changed, 56 insertions(+), 62 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index ae9e0f660570..c4d08d97267f 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -138,11 +138,22 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi private def firstTry(tp1: Type, tp2: Type): Boolean = { tp2 match { case tp2: NamedType => - def isHKSubType = tp2.name == tpnme.Apply && { - val lambda2 = tp2.prefix.LambdaClass(forcing = true) - lambda2.exists && !tp1.isLambda && - tp1.testLifted(lambda2.typeParams, isSubType(_, tp2.prefix)) - } + def compareHKOrAlias(info1: Type) = + tp2.name == tpnme.Apply && { + val lambda2 = tp2.prefix.LambdaClass(forcing = true) + lambda2.exists && !tp1.isLambda && + tp1.testLifted(lambda2.typeParams, isSubType(_, tp2.prefix)) + } || { + tp2.info match { + case info2: TypeAlias => isSubType(tp1, info2.alias) + case _ => + info1 match { + case info1: TypeAlias => isSubType(info1.alias, tp2) + case NoType => secondTry(tp1, tp2) + case _ => thirdTryNamed(tp1, tp2) + } + } + } def compareNamed = { implicit val ctx: Context = this.ctx // Dotty deviation: implicits need explicit type tp1 match { @@ -173,9 +184,10 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi (tp1.signature == tp1.signature) && !tp1.isInstanceOf[WithFixedSym] && !tp2.isInstanceOf[WithFixedSym] - ) || isHKSubType || secondTryNamed(tp1, tp2) - case _ => - isHKSubType || secondTry(tp1, tp2) + ) || + compareHKOrAlias(tp1.info) + case _ => + compareHKOrAlias(NoType) } } compareNamed @@ -238,7 +250,10 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi private def secondTry(tp1: Type, tp2: Type): Boolean = tp1 match { case tp1: NamedType => - secondTryNamed(tp1, tp2) + tp1.info match { + case info1: TypeAlias => isSubType(info1.alias, tp2) + case _ => thirdTry(tp1, tp2) + } case tp1: PolyParam => def flagNothingBound = { if ((!frozenConstraint) && @@ -295,58 +310,31 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi thirdTry(tp1, tp2) } - private def secondTryNamed(tp1: NamedType, tp2: Type): Boolean = tp1.info match { - // There was the following code, which was meant to implement this logic: - // If x has type A | B, then x.type <: C if - // x.type <: C assuming x has type A, and - // x.type <: C assuming x has type B. - // But it did not work, because derivedRef would always give back the same - // type and cache the denotation. So it ended up copmparing just one branch. - // The code seems to be unncessary for the tests and does not seems to help performance. - // So it is commented out. If we ever need to come back to this, we would have - // to create unchached TermRefs in order to avoid cross talk between the branches. - /* - case OrType(tp11, tp12) => - val sd = tp1.denot.asSingleDenotation - def derivedRef(tp: Type) = - NamedType(tp1.prefix, tp1.name, sd.derivedSingleDenotation(sd.symbol, tp)) - secondTry(OrType.make(derivedRef(tp11), derivedRef(tp12)), tp2) - */ - case TypeBounds(lo1, hi1) => - val gbounds1 = ctx.gadt.bounds(tp1.symbol) - if (gbounds1 != null) - isSubTypeWhenFrozen(gbounds1.hi, tp2) || - narrowGADTBounds(tp1, tp2, fromBelow = false) || - thirdTry(tp1, tp2) - else if (lo1 eq hi1) isSubType(hi1, tp2) - else thirdTry(tp1, tp2) + private def thirdTryNamed(tp1: Type, tp2: NamedType): Boolean = tp2.info match { + case TypeBounds(lo2, hi2) => + def compareGADT: Boolean = { + val gbounds2 = ctx.gadt.bounds(tp2.symbol) + (gbounds2 != null) && + (isSubTypeWhenFrozen(tp1, gbounds2.lo) || + narrowGADTBounds(tp2, tp1, isLowerBound = true)) + } + ((frozenConstraint || !isCappable(tp1)) && isSubType(tp1, lo2) || + compareGADT || + fourthTry(tp1, tp2)) + case _ => - thirdTry(tp1, tp2) + 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 => - def compareNamed: Boolean = tp2.info match { - case TypeBounds(lo2, hi2) => - val gbounds2 = ctx.gadt.bounds(tp2.symbol) - if (gbounds2 != null) - isSubTypeWhenFrozen(tp1, gbounds2.lo) || - narrowGADTBounds(tp2, tp1, fromBelow = true) || - fourthTry(tp1, tp2) - else - ((frozenConstraint || !isCappable(tp1)) && isSubType(tp1, lo2) - || 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) - } - compareNamed + thirdTryNamed(tp1, tp2) case tp2: RefinedType => def compareRefined: Boolean = { val tp1w = tp1.widen @@ -437,7 +425,13 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi case tp1: TypeRef => tp1.info match { case TypeBounds(lo1, hi1) => - isSubType(hi1, tp2) + def compareGADT = { + val gbounds1 = ctx.gadt.bounds(tp1.symbol) + (gbounds1 != null) && + (isSubTypeWhenFrozen(gbounds1.hi, tp2) || + narrowGADTBounds(tp1, tp2, isLowerBound = false)) + } + isSubType(hi1, tp2) || compareGADT case _ => def isNullable(tp: Type): Boolean = tp.dealias match { case tp: TypeRef => tp.symbol.isNullableClass @@ -634,15 +628,15 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi //}) } - private def narrowGADTBounds(tr: NamedType, bound: Type, fromBelow: Boolean): Boolean = + private def narrowGADTBounds(tr: NamedType, bound: Type, isLowerBound: Boolean): Boolean = ctx.mode.is(Mode.GADTflexible) && { val tparam = tr.symbol - val bound1 = deSkolemize(bound, toSuper = fromBelow) - println(s"narrow gadt bound of $tparam: ${tparam.info} from ${if (fromBelow) "below" else "above"} to $bound1 ${bound1.isRef(tparam)}") + val bound1 = deSkolemize(bound, toSuper = isLowerBound) + println(s"narrow gadt bound of $tparam: ${tparam.info} from ${if (isLowerBound) "below" else "above"} to $bound1 ${bound1.isRef(tparam)}") !bound1.isRef(tparam) && { val oldBounds = ctx.gadt.bounds(tparam) val newBounds = - if (fromBelow) TypeBounds(oldBounds.lo | bound1, oldBounds.hi) + if (isLowerBound) TypeBounds(oldBounds.lo | bound1, oldBounds.hi) else TypeBounds(oldBounds.lo, oldBounds.hi & bound1) isSubType(newBounds.lo, newBounds.hi) && { ctx.gadt.setBounds(tparam, newBounds); true } From 01f1a26bbfa6763eba960ac71f9d71cebed478df Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 19 Jan 2015 12:09:01 +0100 Subject: [PATCH 56/72] Refactorings in TypeComparer fourthTry Mostly cosmetic, to make code clearer. --- src/dotty/tools/dotc/core/TypeComparer.scala | 43 +++++++++----------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index c4d08d97267f..f8e4d3e4b986 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -411,12 +411,6 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi false } compareClassInfo - case JavaArrayType(elem2) => - def compareJavaArray = tp1 match { - case JavaArrayType(elem1) => isSubType(elem1, elem2) - case _ => fourthTry(tp1, tp2) - } - compareJavaArray case _ => fourthTry(tp1, tp2) } @@ -444,30 +438,33 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi (tp1.symbol eq NullClass) && isNullable(tp2) } case tp1: SingletonType => - isNewSubType(tp1.underlying.widenExpr, tp2) || { - // if tp2 == p.type and p: q.type then try tp1 <:< q.type as a last effort. - tp2 match { - case tp2: TermRef => - tp2.info match { - case tp2i: TermRef => - isSubType(tp1, tp2i) - case ExprType(tp2i: TermRef) if (ctx.phase.id > ctx.gettersPhase.id) => - // After getters, val x: T becomes def x: T - isSubType(tp1, tp2i) - case _ => - false - } - case _ => - false - } + /** 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 match { + case tp2i: TermRef => + isSubType(tp1, tp2i) + case ExprType(tp2i: TermRef) if (ctx.phase.id > ctx.gettersPhase.id) => + // After getters, val x: T becomes def x: T + isSubType(tp1, tp2i) + case _ => + false + } + case _ => + false } + isNewSubType(tp1.underlying.widenExpr, tp2) || comparePaths case tp1: RefinedType => isNewSubType(tp1.parent, tp2) || needsEtaLift(tp2, tp1) && tp2.testLifted(tp1.typeParams, isSubType(tp1, _)) case AndType(tp11, tp12) => eitherIsSubType(tp11, tp2, tp12, tp2) case JavaArrayType(elem1) => - tp2 isRef ObjectClass + def compareJavaArray = tp2 match { + case JavaArrayType(elem2) => isSubType(elem1, elem2) + case _ => tp2 isRef ObjectClass + } + compareJavaArray case _ => false } From 68d58a651ee5a7853e58d40fa1decadd10df110c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 19 Jan 2015 12:17:24 +0100 Subject: [PATCH 57/72] Move PolyParam test later in isSubType and simplify addConstraint Motivation: Would like to profit from normalizations done before, so that we do not have to redo them in addConstraint. Also: eliminate solvedConstraint; it is no longer needed and was always false. --- .../tools/dotc/core/ConstraintHandling.scala | 24 +++++------ src/dotty/tools/dotc/core/TypeComparer.scala | 40 +++++++------------ 2 files changed, 24 insertions(+), 40 deletions(-) diff --git a/src/dotty/tools/dotc/core/ConstraintHandling.scala b/src/dotty/tools/dotc/core/ConstraintHandling.scala index ff7ffdfb16ae..e79dc28b9a7b 100644 --- a/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -197,8 +197,6 @@ trait ConstraintHandling { finally constraint = saved } - protected def solvedConstraint = false - /** The current bounds of type parameter `param` */ final def bounds(param: PolyParam): TypeBounds = constraint.entry(param) match { case bounds: TypeBounds => bounds @@ -228,28 +226,24 @@ trait ConstraintHandling { isSubTypeWhenFrozen(tp, bounds(param).lo) final def canConstrain(param: PolyParam): Boolean = - !frozenConstraint && !solvedConstraint && (constraint contains param) + !frozenConstraint && (constraint contains param) + /** Add constraint `param <: bond` if `fromBelow` is true, `param >: bound` otherwise. + * `bound` is assumed to be in normalized form, as specified in `firstTry` and + * `secondTry` of `TypeComparer`. In particular, it should not be an alias type, + * lazy ref, typevar, wildcard type, error type. In addition, upper bounds may + * not be AndTypes and lower bounds may not be OrTypes. + */ protected def addConstraint(param: PolyParam, bound: Type, fromBelow: Boolean): Boolean = { def description = i"constr $param ${if (fromBelow) ">:" else "<:"} $bound:\n$constraint" checkPropagated(s"adding $description")(true) checkPropagated(s"added $description") { addConstraintInvocations += 1 - try bound.dealias.stripTypeVar match { + try bound match { case bound: PolyParam if constraint contains bound => if (fromBelow) addLess(bound, param) else addLess(param, bound) - case bound: AndOrType if fromBelow != bound.isAnd => - addConstraint(param, bound.tp1, fromBelow) && addConstraint(param, bound.tp2, fromBelow) - case bound: WildcardType => - bound.optBounds match { - case TypeBounds(lo, hi) => addConstraint(param, if (fromBelow) lo else hi, fromBelow) - case NoType => true - } - case bound: ErrorType => - true case _ => - if (fromBelow) addLowerBound(param, bound) - else addUpperBound(param, bound) + if (fromBelow) addLowerBound(param, bound) else addUpperBound(param, bound) } finally addConstraintInvocations -= 1 } diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index f8e4d3e4b986..292c83a6df4b 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -193,18 +193,6 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi compareNamed case tp2: ProtoType => isMatchedByProto(tp2, tp1) - case tp2: PolyParam => - def comparePolyParam = - tp2 == tp1 || { - if (solvedConstraint && (constraint contains tp2)) isSubType(tp1, bounds(tp2).lo) - else - (ctx.mode is Mode.TypevarsMissContext) || - constraintImpliesSuper(tp2, tp1) || { - if (canConstrain(tp2)) addConstraint(tp2, tp1.widenExpr, fromBelow = true) - else secondTry(tp1, tp2) - } - } - comparePolyParam case tp2: BoundType => tp2 == tp1 || secondTry(tp1, tp2) case tp2: TypeVar => @@ -266,15 +254,11 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi true } def comparePolyParam = - tp1 == tp2 || { - if (solvedConstraint && (constraint contains tp1)) isSubType(bounds(tp1).lo, tp2) - else - (ctx.mode is Mode.TypevarsMissContext) || - constraintImpliesSub(tp1, tp2) || { - if (canConstrain(tp1)) addConstraint(tp1, tp2, fromBelow = false) && flagNothingBound - else thirdTry(tp1, tp2) - } - } + (ctx.mode is Mode.TypevarsMissContext) || + constraintImpliesSub(tp1, tp2) || { + if (canConstrain(tp1)) addConstraint(tp1, tp2, fromBelow = false) && flagNothingBound + else thirdTry(tp1, tp2) + } comparePolyParam case tp1: ThisType => tp2 match { @@ -288,8 +272,6 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi case tp2: SkolemType if tp1 == tp2 => true case _ => thirdTry(tp1, tp2) } - case tp1: BoundType => - tp1 == tp2 || thirdTry(tp1, tp2) case tp1: TypeVar => (tp1 eq tp2) || isSubType(tp1.underlying, tp2) case tp1: WildcardType => @@ -335,6 +317,14 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi 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) || + constraintImpliesSuper(tp2, tp1) || { + if (canConstrain(tp2)) addConstraint(tp2, tp1.widenExpr, fromBelow = true) + else fourthTry(tp1, tp2) + } + comparePolyParam case tp2: RefinedType => def compareRefined: Boolean = { val tp1w = tp1.widen @@ -599,7 +589,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi /** Defer constraining type variables when compared against prototypes */ def isMatchedByProto(proto: ProtoType, tp: Type) = tp.stripTypeVar match { - case tp: PolyParam if !solvedConstraint && (constraint contains tp) => true + case tp: PolyParam if constraint contains tp => true case _ => proto.isMatchedBy(tp) } @@ -610,7 +600,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi * type variable with (the corresponding type in) `tp2` instead. */ private def isCappable(tp: Type): Boolean = tp match { - case tp: PolyParam => !solvedConstraint && (constraint contains tp) + case tp: PolyParam => constraint contains tp case tp: TypeProxy => isCappable(tp.underlying) case tp: AndOrType => isCappable(tp.tp1) || isCappable(tp.tp2) case _ => false From b9d018e7af67428c7fff74ae5ed0c55af9f621ff Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 19 Jan 2015 12:49:57 +0100 Subject: [PATCH 58/72] Make checkPropagated configurable Only run if a config option is set. --- src/dotty/tools/dotc/config/Config.scala | 6 ++++++ src/dotty/tools/dotc/core/ConstraintHandling.scala | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/config/Config.scala b/src/dotty/tools/dotc/config/Config.scala index 81c1af5c9a84..db0c3ce4aa27 100644 --- a/src/dotty/tools/dotc/config/Config.scala +++ b/src/dotty/tools/dotc/config/Config.scala @@ -26,6 +26,12 @@ object Config { */ final val checkConstraintsSatisfiable = false + /** Check that each constraint is fully propagated. i.e. + * If P <: Q then the upper bound of P is a subtype of the upper bound of Q + * and the lower bound of Q is a subtype of the lower bound of P. + */ + final val checkConstraintsPropagated = false + /** Type comparer will fail with an assert if the upper bound * of a constrained parameter becomes Nothing. This should be turned * on only for specific debugging as normally instantiation to Nothing diff --git a/src/dotty/tools/dotc/core/ConstraintHandling.scala b/src/dotty/tools/dotc/core/ConstraintHandling.scala index e79dc28b9a7b..28a0c87a2761 100644 --- a/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -236,7 +236,7 @@ trait ConstraintHandling { */ protected def addConstraint(param: PolyParam, bound: Type, fromBelow: Boolean): Boolean = { def description = i"constr $param ${if (fromBelow) ">:" else "<:"} $bound:\n$constraint" - checkPropagated(s"adding $description")(true) + //checkPropagated(s"adding $description")(true) // DEBUG in case following fails checkPropagated(s"added $description") { addConstraintInvocations += 1 try bound match { @@ -250,7 +250,7 @@ trait ConstraintHandling { } def checkPropagated(msg: => String)(result: Boolean): Boolean = { - if (result && addConstraintInvocations == 0) { + if (Config.checkConstraintsPropagated && result && addConstraintInvocations == 0) { frozenConstraint = true for (p <- constraint.domainParams) { for (u <- constraint.upper(p)) From 963af8e852a4234b7739b881dda997a88843ad33 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 19 Jan 2015 12:59:40 +0100 Subject: [PATCH 59/72] Streamline TypeComparer/ConstraintHandling interface. --- .../tools/dotc/core/ConstraintHandling.scala | 12 +++------- src/dotty/tools/dotc/core/TypeComparer.scala | 22 +++++++++---------- 2 files changed, 13 insertions(+), 21 deletions(-) diff --git a/src/dotty/tools/dotc/core/ConstraintHandling.scala b/src/dotty/tools/dotc/core/ConstraintHandling.scala index 28a0c87a2761..692039b70a83 100644 --- a/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -203,8 +203,7 @@ trait ConstraintHandling { case _ => param.binder.paramBounds(param.paramNum) } - def initialize(pt: PolyType): Boolean = { - //println(i"INIT**! $pt") + def initialize(pt: PolyType): Boolean = checkPropagated(i"initialized $pt") { pt.paramNames.indices.forall { i => val param = PolyParam(pt, i) @@ -217,14 +216,8 @@ trait ConstraintHandling { upper.forall(addOneLowerBound(_, bounds.lo)) } } - } - - protected def constraintImpliesSub(param: PolyParam, tp: Type): Boolean = - isSubTypeWhenFrozen(bounds(param).hi, tp) - - protected def constraintImpliesSuper(param: PolyParam, tp: Type): Boolean = - isSubTypeWhenFrozen(tp, bounds(param).lo) + /** Can `param` be constrained with new bounds? */ final def canConstrain(param: PolyParam): Boolean = !frozenConstraint && (constraint contains param) @@ -249,6 +242,7 @@ trait ConstraintHandling { } } + /** Check that constraint is fully propagated. See comment in Config.checkConstraintsPropagated */ def checkPropagated(msg: => String)(result: Boolean): Boolean = { if (Config.checkConstraintsPropagated && result && addConstraintInvocations == 0) { frozenConstraint = true diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index 292c83a6df4b..3e6f59939631 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -244,9 +244,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi } case tp1: PolyParam => def flagNothingBound = { - if ((!frozenConstraint) && - (tp2 isRef defn.NothingClass) && - state.isGlobalCommittable) { + if (!frozenConstraint && tp2.isRef(defn.NothingClass) && state.isGlobalCommittable) { def msg = s"!!! instantiated to Nothing: $tp1, constraint = ${constraint.show}" if (Config.flagInstantiationToNothing) assert(false, msg) else ctx.log(msg) @@ -255,10 +253,10 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi } def comparePolyParam = (ctx.mode is Mode.TypevarsMissContext) || - constraintImpliesSub(tp1, tp2) || { - if (canConstrain(tp1)) addConstraint(tp1, tp2, fromBelow = false) && flagNothingBound - else thirdTry(tp1, tp2) - } + isSubTypeWhenFrozen(bounds(tp1).hi, tp2) || { + if (canConstrain(tp1)) addConstraint(tp1, tp2, fromBelow = false) && flagNothingBound + else thirdTry(tp1, tp2) + } comparePolyParam case tp1: ThisType => tp2 match { @@ -320,10 +318,10 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi case tp2: PolyParam => def comparePolyParam = (ctx.mode is Mode.TypevarsMissContext) || - constraintImpliesSuper(tp2, tp1) || { - if (canConstrain(tp2)) addConstraint(tp2, tp1.widenExpr, fromBelow = true) - else fourthTry(tp1, tp2) - } + isSubTypeWhenFrozen(tp1, bounds(tp2).lo) || { + if (canConstrain(tp2)) addConstraint(tp2, tp1.widenExpr, fromBelow = true) + else fourthTry(tp1, tp2) + } comparePolyParam case tp2: RefinedType => def compareRefined: Boolean = { @@ -413,7 +411,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi val gbounds1 = ctx.gadt.bounds(tp1.symbol) (gbounds1 != null) && (isSubTypeWhenFrozen(gbounds1.hi, tp2) || - narrowGADTBounds(tp1, tp2, isLowerBound = false)) + narrowGADTBounds(tp1, tp2, isLowerBound = false)) } isSubType(hi1, tp2) || compareGADT case _ => From eed18aab0d52bd2f7a1cb17433759b51890e40aa Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 19 Jan 2015 13:34:43 +0100 Subject: [PATCH 60/72] Small polishings for isSubType Drop unused pattern vars, redundant tests, indentation changes. --- src/dotty/tools/dotc/core/TypeComparer.scala | 187 +++++++++---------- 1 file changed, 93 insertions(+), 94 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index 3e6f59939631..6532d51bff88 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -135,105 +135,104 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi } } - private def firstTry(tp1: Type, tp2: Type): Boolean = { - tp2 match { - case tp2: NamedType => - def compareHKOrAlias(info1: Type) = - tp2.name == tpnme.Apply && { - val lambda2 = tp2.prefix.LambdaClass(forcing = true) - lambda2.exists && !tp1.isLambda && - tp1.testLifted(lambda2.typeParams, isSubType(_, tp2.prefix)) - } || { - tp2.info match { - case info2: TypeAlias => isSubType(tp1, info2.alias) - case _ => - info1 match { - case info1: TypeAlias => isSubType(info1.alias, tp2) - case NoType => secondTry(tp1, tp2) - case _ => thirdTryNamed(tp1, tp2) - } + private def firstTry(tp1: Type, tp2: Type): Boolean = tp2 match { + case tp2: NamedType => + def compareHKOrAlias(info1: Type) = + tp2.name == tpnme.Apply && { + val lambda2 = tp2.prefix.LambdaClass(forcing = true) + lambda2.exists && !tp1.isLambda && + tp1.testLifted(lambda2.typeParams, isSubType(_, tp2.prefix)) + } || { + tp2.info match { + case info2: TypeAlias => isSubType(tp1, info2.alias) + case _ => info1 match { + case info1: TypeAlias => isSubType(info1.alias, tp2) + case NoType => secondTry(tp1, tp2) + case _ => thirdTryNamed(tp1, tp2) } } - def compareNamed = { - implicit val ctx: Context = this.ctx // Dotty deviation: implicits need explicit type - tp1 match { - case tp1: NamedType => - val sym1 = tp1.symbol - (if ((sym1 ne NoSymbol) && (sym1 eq tp2.symbol)) ( - ctx.erasedTypes - || sym1.isStaticOwner - || { // Implements: A # X <: B # X - // if either A =:= B (i.e. A <: B and B <: A), or the following three conditions hold: - // 1. X is a class type, - // 2. B is a class type without abstract type members. - // 3. A <: B. - // Dealiasing is taken care of elsewhere. - val pre1 = tp1.prefix - val pre2 = tp2.prefix - ( isSameType(pre1, pre2) - || sym1.isClass - && pre2.classSymbol.exists - && pre2.abstractTypeMembers.isEmpty - && isSubType(pre1, pre2) - ) - } - ) - else - (tp1.name eq tp2.name) && - isSameType(tp1.prefix, tp2.prefix) && - (tp1.signature == tp1.signature) && - !tp1.isInstanceOf[WithFixedSym] && - !tp2.isInstanceOf[WithFixedSym] - ) || - compareHKOrAlias(tp1.info) - case _ => - compareHKOrAlias(NoType) - } } - compareNamed - 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 + def compareNamed = { + implicit val ctx: Context = this.ctx // Dotty deviation: implicits need explicit type + tp1 match { + case tp1: NamedType => + val sym1 = tp1.symbol + (if ((sym1 ne NoSymbol) && (sym1 eq tp2.symbol)) + ctx.erasedTypes || sym1.isStaticOwner || + { // Implements: A # X <: B # X + // if either A =:= B (i.e. A <: B and B <: A), or the following three conditions hold: + // 1. X is a class type, + // 2. B is a class type without abstract type members. + // 3. A <: B. + // Dealiasing is taken care of elsewhere. + val pre1 = tp1.prefix + val pre2 = tp2.prefix + isSameType(pre1, pre2) || + sym1.isClass && + pre2.classSymbol.exists && + pre2.abstractTypeMembers.isEmpty && + isSubType(pre1, pre2) + } + else + (tp1.name eq tp2.name) && + isSameType(tp1.prefix, tp2.prefix) && + (tp1.signature == tp1.signature) && + !tp1.isInstanceOf[WithFixedSym] && + !tp2.isInstanceOf[WithFixedSym] + ) || + compareHKOrAlias(tp1.info) + case _ => + compareHKOrAlias(NoType) } - compareWild - case tp2: LazyRef => - isSubType(tp1, tp2.ref) - case tp2: AnnotatedType => - isSubType(tp1, tp2.tpe) // todo: refine? - case tp2: ThisType => + } + compareNamed + 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 => + 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. - tp1.cls.classInfo.selfType.derivesFrom(tp2.cls) && - tp2.cls.classInfo.selfType.derivesFrom(tp1.cls) + val cls1 = tp1.cls + cls1.classInfo.selfType.derivesFrom(cls2) && + cls2.classInfo.selfType.derivesFrom(cls1) case tp1: TermRef if tp2.cls eq tp1.symbol.moduleClass => - isSubType(tp1.prefix, tp2.cls.owner.thisType) - case _ => - secondTry(tp1, tp2) - } - case tp2: SuperType => - tp1 match { - case tp1: SuperType => - isSubType(tp1.thistpe, tp2.thistpe) && - isSameType(tp1.supertpe, tp2.supertpe) + isSubType(tp1.prefix, cls2.owner.thisType) case _ => secondTry(tp1, tp2) } - case AndType(tp21, tp22) => - isSubType(tp1, tp21) && isSubType(tp1, tp22) - case ErrorType => - true - case _ => - secondTry(tp1, tp2) - } + } + compareThis + case tp2: SuperType => + tp1 match { + case tp1: SuperType => + isSubType(tp1.thistpe, tp2.thistpe) && + isSameType(tp1.supertpe, tp2.supertpe) + case _ => + secondTry(tp1, tp2) + } + case AndType(tp21, tp22) => + isSubType(tp1, tp21) && isSubType(tp1, tp22) + case ErrorType => + true + case _ => + secondTry(tp1, tp2) } private def secondTry(tp1: Type, tp2: Type): Boolean = tp1 match { @@ -252,7 +251,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi true } def comparePolyParam = - (ctx.mode is Mode.TypevarsMissContext) || + ctx.mode.is(Mode.TypevarsMissContext) || isSubTypeWhenFrozen(bounds(tp1).hi, tp2) || { if (canConstrain(tp1)) addConstraint(tp1, tp2, fromBelow = false) && flagNothingBound else thirdTry(tp1, tp2) @@ -271,7 +270,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi case _ => thirdTry(tp1, tp2) } case tp1: TypeVar => - (tp1 eq tp2) || isSubType(tp1.underlying, tp2) + isSubType(tp1.underlying, tp2) case tp1: WildcardType => def compareWild = tp1.optBounds match { case TypeBounds(lo, _) => isSubType(lo, tp2) @@ -291,7 +290,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi } private def thirdTryNamed(tp1: Type, tp2: NamedType): Boolean = tp2.info match { - case TypeBounds(lo2, hi2) => + case TypeBounds(lo2, _) => def compareGADT: Boolean = { val gbounds2 = ctx.gadt.bounds(tp2.symbol) (gbounds2 != null) && @@ -383,7 +382,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi def compareTypeBounds = tp1 match { case tp1 @ TypeBounds(lo1, hi1) => (tp2.variance > 0 && tp1.variance >= 0 || isSubType(lo2, lo1)) && - (tp2.variance < 0 && tp1.variance <= 0 || isSubType(hi1, hi2)) + (tp2.variance < 0 && tp1.variance <= 0 || isSubType(hi1, hi2)) case tp1: ClassInfo => val tt = tp1.typeRef isSubType(lo2, tt) && isSubType(tt, hi2) @@ -406,7 +405,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi private def fourthTry(tp1: Type, tp2: Type): Boolean = tp1 match { case tp1: TypeRef => tp1.info match { - case TypeBounds(lo1, hi1) => + case TypeBounds(_, hi1) => def compareGADT = { val gbounds1 = ctx.gadt.bounds(tp1.symbol) (gbounds1 != null) && @@ -423,7 +422,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi case _ => false } (tp1.symbol eq NothingClass) && tp2.isInstanceOf[ValueType] || - (tp1.symbol eq NullClass) && isNullable(tp2) + (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.*/ @@ -617,7 +616,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi ctx.mode.is(Mode.GADTflexible) && { val tparam = tr.symbol val bound1 = deSkolemize(bound, toSuper = isLowerBound) - println(s"narrow gadt bound of $tparam: ${tparam.info} from ${if (isLowerBound) "below" else "above"} to $bound1 ${bound1.isRef(tparam)}") + typr.println(s"narrow gadt bound of $tparam: ${tparam.info} from ${if (isLowerBound) "below" else "above"} to $bound1 ${bound1.isRef(tparam)}") !bound1.isRef(tparam) && { val oldBounds = ctx.gadt.bounds(tparam) val newBounds = From b45c69fb992acf1bbd9511408193d6d92ef7089a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 20 Jan 2015 22:25:09 +0100 Subject: [PATCH 61/72] Plugging a possible hole in unify unify now does a full `replace` of one parameter with the other. The previous scheme of just updating the entry of a parameter risks breaking constraint invariants. In particular, other constraint bounds can still refer to the eliminated parameter. This might not be a problem, but then we should be systeamtically leaving these references in everywhere. In any case it seems better to harmonize what unify and replace are doing. --- src/dotty/tools/dotc/core/TrackingConstraint.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/core/TrackingConstraint.scala b/src/dotty/tools/dotc/core/TrackingConstraint.scala index 5a77b88fcd0d..37f4d5a6ea5a 100644 --- a/src/dotty/tools/dotc/core/TrackingConstraint.scala +++ b/src/dotty/tools/dotc/core/TrackingConstraint.scala @@ -286,7 +286,7 @@ class TrackingConstraint(private val myMap: ParamInfo, def unify(p1: PolyParam, p2: PolyParam)(implicit ctx: Context): This = { val p1Bounds = (nonParamBounds(p1) & nonParamBounds(p2)).substParam(p2, p1) - this.updateEntry(p1, p1Bounds).updateEntry(p2, p1) + updateEntry(p1, p1Bounds).replace(p2, p1) } // ---------- Removals ------------------------------------------------------------ From fd3a5beb5176581c07badaaa15beb88dc06752ed Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 23 Jan 2015 01:04:00 +0100 Subject: [PATCH 62/72] New constraint method: narrowBound Allows to merge the functionality of addOneLowerBound and addOneUpperBound in ConstraintHandling. --- src/dotty/tools/dotc/core/Constraint.scala | 6 ++ .../tools/dotc/core/ConstraintHandling.scala | 69 +++++++++---------- .../tools/dotc/core/TrackingConstraint.scala | 9 +++ 3 files changed, 48 insertions(+), 36 deletions(-) diff --git a/src/dotty/tools/dotc/core/Constraint.scala b/src/dotty/tools/dotc/core/Constraint.scala index 10e6d0d5a997..873a3c913adb 100644 --- a/src/dotty/tools/dotc/core/Constraint.scala +++ b/src/dotty/tools/dotc/core/Constraint.scala @@ -106,6 +106,12 @@ abstract class Constraint extends Showable { * of the parameter elsewhere in the constraint by type `tp`. */ def replace(param: PolyParam, tp: Type)(implicit ctx: Context): This + + /** Narrow one of the bounds of type parameter `param` + * If `isUpper` is true, ensure that `param <: `bound`, otherwise ensure + * that `param >: bound`. + */ + def narrowBound(param: PolyParam, bound: Type, isUpper: Boolean)(implicit ctx: Context): This /** Is entry associated with `pt` removable? * @param removedParam The index of a parameter which is still present in the diff --git a/src/dotty/tools/dotc/core/ConstraintHandling.scala b/src/dotty/tools/dotc/core/ConstraintHandling.scala index 692039b70a83..6371a176b3c2 100644 --- a/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -44,33 +44,17 @@ trait ConstraintHandling { /** If the constraint is frozen we cannot add new bounds to the constraint. */ protected var frozenConstraint = false - - private def addOneUpperBound(param: PolyParam, bound: Type): Boolean = - constraint.entry(param) match { - case oldBounds @ TypeBounds(lo, hi) => - val newHi = hi & bound - (newHi eq hi) || { - val newBounds = oldBounds.derivedTypeBounds(lo, newHi) - constraint = constraint.updateEntry(param, newBounds) - isSubType(lo, newHi) - } - case _ => - true - } - - private def addOneLowerBound(param: PolyParam, bound: Type): Boolean = - constraint.entry(param) match { - case oldBounds @ TypeBounds(lo, hi) => - val newLo = lo | bound - (newLo eq lo) || { - val newBounds = oldBounds.derivedTypeBounds(newLo, hi) - constraint = constraint.updateEntry(param, newBounds) - isSubType(newLo, hi) - } - case _ => - true - } + private def addOneBound(param: PolyParam, bound: Type, isUpper: Boolean): Boolean = + !constraint.contains(param) || { + val c1 = constraint.narrowBound(param, bound, isUpper) + (c1 eq constraint) || { + constraint = c1 + val TypeBounds(lo, hi) = constraint.entry(param) + isSubType(lo, hi) + } + } + protected def addUpperBound(param: PolyParam, bound: Type): Boolean = { def description = i"constraint $param <: $bound to\n$constraint" if (bound.isRef(defn.NothingClass) && ctx.typerState.isGlobalCommittable) { @@ -80,7 +64,9 @@ trait ConstraintHandling { } constr.println(i"adding $description") val lower = constraint.lower(param) - val res = addOneUpperBound(param, bound) && lower.forall(addOneUpperBound(_, bound)) + val res = + addOneBound(param, bound, isUpper = true) && + lower.forall(addOneBound(_, bound, isUpper = true)) constr.println(i"added $description = $res") res } @@ -89,7 +75,9 @@ trait ConstraintHandling { def description = i"constraint $param >: $bound to\n$constraint" constr.println(i"adding $description") val upper = constraint.upper(param) - val res = addOneLowerBound(param, bound) && upper.forall(addOneLowerBound(_, bound)) + val res = + addOneBound(param, bound, isUpper = false) && + upper.forall(addOneBound(_, bound, isUpper = false)) constr.println(i"added $description = $res") res } @@ -108,7 +96,8 @@ trait ConstraintHandling { val hi2 = constraint.nonParamBounds(p2).hi constr.println(i"adding $description down1 = $down1, up2 = $up2") constraint = constraint.addLess(p1, p2) - down1.forall(addOneUpperBound(_, hi2)) && up2.forall(addOneLowerBound(_, lo1)) + down1.forall(addOneBound(_, hi2, isUpper = true)) && + up2.forall(addOneBound(_, lo1, isUpper = false)) } constr.println(i"added $description = $res") res @@ -119,6 +108,7 @@ trait ConstraintHandling { */ private def unify(p1: PolyParam, p2: PolyParam): Boolean = { constr.println(s"unifying $p1 $p2") + assert(constraint.isLess(p1, p2)) val down = constraint.exclusiveLower(p2, p1) val up = constraint.exclusiveUpper(p1, p2) constraint = constraint.unify(p1, p2) @@ -126,8 +116,8 @@ trait ConstraintHandling { val lo = bounds.lo val hi = bounds.hi isSubType(lo, hi) && - down.forall(addOneUpperBound(_, hi)) && - up.forall(addOneLowerBound(_, lo)) + down.forall(addOneBound(_, hi, isUpper = true)) && + up.forall(addOneBound(_, lo, isUpper = false)) } protected final def isSubTypeWhenFrozen(tp1: Type, tp2: Type): Boolean = { @@ -203,6 +193,9 @@ trait ConstraintHandling { case _ => param.binder.paramBounds(param.paramNum) } + /** If `p` is a parameter of `pt`, propagate the non-parameter bounds + * of `p` to the parameters known to be less or greater than `p`. + */ def initialize(pt: PolyType): Boolean = checkPropagated(i"initialized $pt") { pt.paramNames.indices.forall { i => @@ -212,8 +205,8 @@ trait ConstraintHandling { val upper = constraint.upper(param) if (lower.nonEmpty && !bounds.lo.isRef(defn.NothingClass) || upper.nonEmpty && !bounds.hi.isRef(defn.AnyClass)) println(i"INIT*** $pt") - lower.forall(addOneUpperBound(_, bounds.hi)) && - upper.forall(addOneLowerBound(_, bounds.lo)) + lower.forall(addOneBound(_, bounds.hi, isUpper = true)) && + upper.forall(addOneBound(_, bounds.lo, isUpper = false)) } } @@ -247,10 +240,14 @@ trait ConstraintHandling { if (Config.checkConstraintsPropagated && result && addConstraintInvocations == 0) { frozenConstraint = true for (p <- constraint.domainParams) { + def check(cond: => Boolean, q: PolyParam, ordering: String, explanation: String): Unit = + assert(cond, i"propagation failure for $p $ordering $q: $explanation\n$msg") for (u <- constraint.upper(p)) - assert(bounds(p).hi <:< bounds(u).hi, i"propagation failure for upper $p subsumes $u\n$msg") - for (l <- constraint.lower(p)) - assert(bounds(l).lo <:< bounds(p).lo, i"propagation failure for lower $p subsumes $l\n$msg") + check(bounds(p).hi <:< bounds(u).hi, u, "<:", "upper bound not propagated") + for (l <- constraint.lower(p)) { + check(bounds(l).lo <:< bounds(p).hi, l, ">:", "lower bound not propagated") + check(constraint.isLess(l, p), l, ">:", "reverse ordering (<:) missing") + } } frozenConstraint = false } diff --git a/src/dotty/tools/dotc/core/TrackingConstraint.scala b/src/dotty/tools/dotc/core/TrackingConstraint.scala index 37f4d5a6ea5a..3f15f2b37cfe 100644 --- a/src/dotty/tools/dotc/core/TrackingConstraint.scala +++ b/src/dotty/tools/dotc/core/TrackingConstraint.scala @@ -288,6 +288,15 @@ class TrackingConstraint(private val myMap: ParamInfo, val p1Bounds = (nonParamBounds(p1) & nonParamBounds(p2)).substParam(p2, p1) updateEntry(p1, p1Bounds).replace(p2, p1) } + + def narrowBound(param: PolyParam, bound: Type, isUpper: Boolean)(implicit ctx: Context): This = { + val oldBounds @ TypeBounds(lo, hi) = nonParamBounds(param) + val newBounds = + if (isUpper) oldBounds.derivedTypeBounds(lo, hi & bound) + else oldBounds.derivedTypeBounds(lo | bound, hi) + if (newBounds eq oldBounds) this + else updateEntry(param, newBounds) + } // ---------- Removals ------------------------------------------------------------ From 80618207e1ec169178f037bfab01a2b3bbd27ebe Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 24 Jan 2015 11:18:39 +0100 Subject: [PATCH 63/72] New constraint implementation: OrderingConstraint This is one both a bit simpler and a faster than TrackingConstraint. Main change is to replace the 2-d bitmap for ordering with two SimpleMaps which record for each parameter the smaller and greater parameters. This is faster in practice because the ordering relation is sparse. --- src/dotty/tools/dotc/config/Config.scala | 2 +- src/dotty/tools/dotc/core/Constraint.scala | 2 +- .../tools/dotc/core/OrderingConstraint.scala | 481 ++++++++++++++++++ .../tools/dotc/core/TrackingConstraint.scala | 122 ++--- src/dotty/tools/dotc/core/TyperState.scala | 3 +- 5 files changed, 546 insertions(+), 64 deletions(-) create mode 100644 src/dotty/tools/dotc/core/OrderingConstraint.scala diff --git a/src/dotty/tools/dotc/config/Config.scala b/src/dotty/tools/dotc/config/Config.scala index db0c3ce4aa27..fe4e888291cd 100644 --- a/src/dotty/tools/dotc/config/Config.scala +++ b/src/dotty/tools/dotc/config/Config.scala @@ -13,7 +13,7 @@ object Config { /** When updating a connstraint bound, check that the constrained parameter * does not appear at the top-level of either of its bounds. */ - final val checkConstraintsNonCyclic = true + final val checkConstraintsNonCyclic = false /** Like `checkConstraintsNonCyclic`, but all constrained parameters * are tested for direct or indirect dependencies, each time a diff --git a/src/dotty/tools/dotc/core/Constraint.scala b/src/dotty/tools/dotc/core/Constraint.scala index 873a3c913adb..cedfb52eb34a 100644 --- a/src/dotty/tools/dotc/core/Constraint.scala +++ b/src/dotty/tools/dotc/core/Constraint.scala @@ -42,7 +42,7 @@ abstract class Constraint extends Showable { def typeVarOfParam(param: PolyParam): Type /** Is it known that `param1 <:< param2`? */ - def isLess(param1: PolyParam, param2: PolyParam)(implicit ctx: Context): Boolean + def isLess(param1: PolyParam, param2: PolyParam): Boolean /** The parameters that are known to be smaller wrt <: than `param` */ def lower(param: PolyParam): List[PolyParam] diff --git a/src/dotty/tools/dotc/core/OrderingConstraint.scala b/src/dotty/tools/dotc/core/OrderingConstraint.scala new file mode 100644 index 000000000000..2deec51a8cd0 --- /dev/null +++ b/src/dotty/tools/dotc/core/OrderingConstraint.scala @@ -0,0 +1,481 @@ +package dotty.tools +package dotc +package core + +import Types._, Contexts._, Symbols._, Decorators._ +import util.SimpleMap +import collection.mutable +import printing.{Printer, Showable} +import printing.Texts._ +import config.Config +import config.Printers._ +import collection.immutable.BitSet +import reflect.ClassTag + +object OrderingConstraint { + + /** The type of `Constraint#boundsMap` */ + type ParamBounds = SimpleMap[PolyType, Array[Type]] + + /** The type of `Constraint#lowerMap`, `Constraint#upperMap` */ + type ParamOrdering = SimpleMap[PolyType, Array[List[PolyParam]]] + + /** A new constraint with given maps */ + private def newConstraint(boundsMap: ParamBounds, lowerMap: ParamOrdering, upperMap: ParamOrdering)(implicit ctx: Context) : OrderingConstraint = { + val result = new OrderingConstraint(boundsMap, lowerMap, upperMap) + if (Config.checkConstraintsNonCyclic) result.checkNonCyclic() + ctx.runInfo.recordConstraintSize(result, result.boundsMap.size) + result + } + + /** A lens for updating a single entry array in one of the three constraint maps */ + abstract class ConstraintLens[T <: AnyRef: ClassTag] { + def entries(c: OrderingConstraint, poly: PolyType): Array[T] + def updateEntries(c: OrderingConstraint, poly: PolyType, entries: Array[T])(implicit ctx: Context): OrderingConstraint + def initial: T + + def apply(c: OrderingConstraint, poly: PolyType, idx: Int) = { + val es = entries(c, poly) + if (es == null) initial else es(idx) + } + + /** The `current` constraint but with the entry for `param` updated to `entry`. + * `current` is used linearly. If it is different from `prev` it is + * known to be dead after the call. Hence it is OK to update destructively + * parts of `current` which are not shared by `prev`. + */ + def update(prev: OrderingConstraint, current: OrderingConstraint, + poly: PolyType, idx: Int, entry: T)(implicit ctx: Context): OrderingConstraint = { + var es = entries(current, poly) + if (es != null && (es(idx) eq entry)) current + else { + val result = + if (es == null) { + es = Array.fill(poly.paramNames.length)(initial) + updateEntries(current, poly, es) + } + else if (es ne entries(prev, poly)) + current // can re-use existing entries array. + else { + es = es.clone + updateEntries(current, poly, es) + } + es(idx) = entry + result + } + } + + def update(prev: OrderingConstraint, current: OrderingConstraint, + param: PolyParam, entry: T)(implicit ctx: Context): OrderingConstraint = + update(prev, current, param.binder, param.paramNum, entry) + + def map(prev: OrderingConstraint, current: OrderingConstraint, + poly: PolyType, idx: Int, f: T => T)(implicit ctx: Context): OrderingConstraint = + update(prev, current, poly, idx, f(apply(current, poly, idx))) + + def map(prev: OrderingConstraint, current: OrderingConstraint, + param: PolyParam, f: T => T)(implicit ctx: Context): OrderingConstraint = + map(prev, current, param.binder, param.paramNum, f) + } + + val boundsLens = new ConstraintLens[Type] { + def entries(c: OrderingConstraint, poly: PolyType): Array[Type] = + c.boundsMap(poly) + def updateEntries(c: OrderingConstraint, poly: PolyType, entries: Array[Type])(implicit ctx: Context): OrderingConstraint = + newConstraint(c.boundsMap.updated(poly, entries), c.lowerMap, c.upperMap) + def initial = NoType + } + + val lowerLens = new ConstraintLens[List[PolyParam]] { + def entries(c: OrderingConstraint, poly: PolyType): Array[List[PolyParam]] = + c.lowerMap(poly) + def updateEntries(c: OrderingConstraint, poly: PolyType, entries: Array[List[PolyParam]])(implicit ctx: Context): OrderingConstraint = + newConstraint(c.boundsMap, c.lowerMap.updated(poly, entries), c.upperMap) + def initial = Nil + } + + val upperLens = new ConstraintLens[List[PolyParam]] { + def entries(c: OrderingConstraint, poly: PolyType): Array[List[PolyParam]] = + c.upperMap(poly) + def updateEntries(c: OrderingConstraint, poly: PolyType, entries: Array[List[PolyParam]])(implicit ctx: Context): OrderingConstraint = + newConstraint(c.boundsMap, c.lowerMap, c.upperMap.updated(poly, entries)) + def initial = Nil + } +} + +import OrderingConstraint._ + +/** Constraint over undetermined type parameters that keeps separate maps to + * reflect parameter orderings. + * @param boundsMap a map from PolyType to arrays. + * Each array contains twice the number of entries as there a type parameters + * in the PolyType. The first half of the array contains the type bounds that constrain the + * polytype's type parameters. The second half might contain type variables that + * track the corresponding parameters, or is left empty (filled with nulls). + * An instantiated type parameter is represented by having its instance type in + * the corresponding array entry. + * + * @param lowerMap a map from PolyTypes to arrays. Each array entry corresponds + * to a parameter P of the polytype; it contains all constrained parameters + * Q that are known to be smaller than P, i.e. P <: Q. + * @param upperMap a map from PolyTypes to arrays. Each array entry corresponds + * to a parameter P of the polytype; it contains all constrained parameters + * Q that are known to be greater than P, i.e. Q <: P. + */ +class OrderingConstraint(private val boundsMap: ParamBounds, + private val lowerMap : ParamOrdering, + private val upperMap : ParamOrdering) extends Constraint { + + type This = OrderingConstraint + + +// ----------- Basic indices -------------------------------------------------- + + /** The number of type parameters in the given entry array */ + private def paramCount(entries: Array[Type]) = entries.length >> 1 + + /** The type variable corresponding to parameter numbered `n`, null if none was created */ + private def typeVar(entries: Array[Type], n: Int): Type = + entries(paramCount(entries) + n) + + /** The `boundsMap` entry corresponding to `param` */ + def entry(param: PolyParam): Type = { + val entries = boundsMap(param.binder) + if (entries == null) NoType + else entries(param.paramNum) + } + +// ----------- Contains tests -------------------------------------------------- + + def contains(pt: PolyType): Boolean = boundsMap(pt) != null + + def contains(param: PolyParam): Boolean = { + val entries = boundsMap(param.binder) + entries != null && entries(param.paramNum).isInstanceOf[TypeBounds] + } + + def contains(tvar: TypeVar): Boolean = { + val origin = tvar.origin + val entries = boundsMap(origin.binder) + val pnum = origin.paramNum + entries != null && isBounds(entries(pnum)) && (typeVar(entries, pnum) eq tvar) + } + + private def isBounds(tp: Type) = tp.isInstanceOf[TypeBounds] + +// ---------- Dependency handling ---------------------------------------------- + + def lower(param: PolyParam): List[PolyParam] = lowerLens(this, param.binder, param.paramNum) + def upper(param: PolyParam): List[PolyParam] = upperLens(this, param.binder, param.paramNum) + + def minLower(param: PolyParam): List[PolyParam] = { + val all = lower(param) + all.filterNot(p => all.exists(isLess(p, _))) + } + + def minUpper(param: PolyParam): List[PolyParam] = { + val all = upper(param) + all.filterNot(p => all.exists(isLess(_, p))) + } + + def exclusiveLower(param: PolyParam, butNot: PolyParam): List[PolyParam] = + lower(param).filterNot(isLess(_, butNot)) + + def exclusiveUpper(param: PolyParam, butNot: PolyParam): List[PolyParam] = + upper(param).filterNot(isLess(butNot, _)) + +// ---------- Info related to PolyParams ------------------------------------------- + + def isLess(param1: PolyParam, param2: PolyParam): Boolean = + upper(param1).contains(param2) + + def nonParamBounds(param: PolyParam): TypeBounds = + entry(param).asInstanceOf[TypeBounds] + + def fullLowerBound(param: PolyParam)(implicit ctx: Context): Type = + (nonParamBounds(param).lo /: minLower(param))(_ | _) + + def fullUpperBound(param: PolyParam)(implicit ctx: Context): Type = + (nonParamBounds(param).hi /: minUpper(param))(_ & _) + + def fullBounds(param: PolyParam)(implicit ctx: Context): TypeBounds = + nonParamBounds(param).derivedTypeBounds(fullLowerBound(param), fullUpperBound(param)) + + def typeVarOfParam(param: PolyParam): Type = { + val entries = boundsMap(param.binder) + if (entries == null) NoType + else { + val tvar = typeVar(entries, param.paramNum) + if (tvar != null) tvar else NoType + } + } + +// ---------- Adding PolyTypes -------------------------------------------------- + + /** The bound type `tp` without dependent parameters + * NoType if type consists only of dependent parameters. + * @param seenFromBelow If true, `bound` is an upper bound, else a lower bound. + */ + private def stripParams(tp: Type, paramBuf: mutable.ListBuffer[PolyParam], + isUpper: Boolean)(implicit ctx: Context): Type = tp match { + case param: PolyParam if contains(param) => + if (!paramBuf.contains(param)) paramBuf += param + NoType + case tp: AndOrType if isUpper == tp.isAnd => + val tp1 = stripParams(tp.tp1, paramBuf, isUpper) + val tp2 = stripParams(tp.tp2, paramBuf, isUpper) + if (tp1.exists) + if (tp2.exists) tp.derivedAndOrType(tp1, tp2) + else tp1 + else tp2 + case _ => + tp + } + + /** The bound type `tp` without dependent parameters. + * A top or bottom type if type consists only of dependent parameters. + * @param seenFromBelow If true, `bound` is an upper bound, else a lower bound. + */ + private def normalizedType(tp: Type, paramBuf: mutable.ListBuffer[PolyParam], + isUpper: Boolean)(implicit ctx: Context): Type = + stripParams(tp, paramBuf, isUpper) + .orElse(if (isUpper) defn.AnyType else defn.NothingType) + + def add(poly: PolyType, tvars: List[TypeVar])(implicit ctx: Context): This = { + assert(!contains(poly)) + val nparams = poly.paramNames.length + val entries1 = new Array[Type](nparams * 2) + poly.paramBounds.copyToArray(entries1, 0) + tvars.copyToArray(entries1, nparams) + newConstraint(boundsMap.updated(poly, entries1), lowerMap, upperMap).init(poly) + } + + private def init(poly: PolyType)(implicit ctx: Context): This = { + var current = this + val loBuf, hiBuf = new mutable.ListBuffer[PolyParam] + var i = 0 + while (i < poly.paramNames.length) { + val param = PolyParam(poly, i) + val bounds = nonParamBounds(param) + val lo = normalizedType(bounds.lo, loBuf, isUpper = false) + val hi = normalizedType(bounds.hi, hiBuf, isUpper = true) + current = boundsLens.update(this, current, poly, i, bounds.derivedTypeBounds(lo, hi)) + current = (current /: loBuf)(order(_, _, param)) + current = (current /: hiBuf)(order(_, param, _)) + loBuf.clear() + hiBuf.clear() + i += 1 + } + if (Config.checkConstraintsNonCyclic) checkNonCyclic() + current + } + +// ---------- Updates ------------------------------------------------------------ + + /** An updated partial order matrix that incorporates `less` and also reflects that `param` relates + * to `p2` wrt <:< if `inOrder` is true, `>:>` otherwise. + */ + private def order(current: This, param1: PolyParam, param2: PolyParam)(implicit ctx: Context): This = + if (param1 == param2 || current.isLess(param1, param2)) this + else { + assert(contains(param1)) + assert(contains(param2)) + val newUpper = param2 :: exclusiveUpper(param2, param1) + val newLower = param1 :: exclusiveLower(param1, param2) + val current1 = (current /: newLower)(upperLens.map(this, _, _, newUpper ::: _)) + val current2 = (current1 /: newUpper)(lowerLens.map(this, _, _, newLower ::: _)) + current2 + } + + def addLess(param1: PolyParam, param2: PolyParam)(implicit ctx: Context): This = + order(this, param1, param2) + + def updateEntry(param: PolyParam, tp: Type)(implicit ctx: Context): This = + boundsLens.update(this, this, param, tp) + + def unify(p1: PolyParam, p2: PolyParam)(implicit ctx: Context): This = { + val p1Bounds = (nonParamBounds(p1) & nonParamBounds(p2)).substParam(p2, p1) + updateEntry(p1, p1Bounds).replace(p2, p1) + } + + def narrowBound(param: PolyParam, bound: Type, isUpper: Boolean)(implicit ctx: Context): This = { + val oldBounds @ TypeBounds(lo, hi) = nonParamBounds(param) + val newBounds = + if (isUpper) oldBounds.derivedTypeBounds(lo, hi & bound) + else oldBounds.derivedTypeBounds(lo | bound, hi) + updateEntry(param, newBounds) + } + +// ---------- Removals ------------------------------------------------------------ + + /** Drop parameter `PolyParam(poly, n)` from `bounds`, + * replacing with Nothing in the lower bound and by `Any` in the upper bound. + */ + private def dropParamIn(bounds: TypeBounds, poly: PolyType, n: Int)(implicit ctx: Context): TypeBounds = { + def drop(tp: Type): Type = tp match { + case tp: AndOrType => + val tp1 = drop(tp.tp1) + val tp2 = drop(tp.tp2) + if (!tp1.exists) tp2 + else if (!tp2.exists) tp1 + else tp + case PolyParam(`poly`, `n`) => NoType + case _ => tp + } + def approx(tp: Type, limit: Type): Type = { + val tp1 = drop(tp) + if (tp1.exists || !tp.exists) tp1 else limit + } + bounds.derivedTypeBounds( + approx(bounds.lo, defn.NothingType), approx(bounds.hi, defn.AnyType)) + } + + def replace(param: PolyParam, tp: Type)(implicit ctx: Context): OrderingConstraint = { + val replacement = tp.dealias.stripTypeVar + if (param == replacement) this + else { + assert(replacement.isValueType) + val poly = param.binder + val idx = param.paramNum + def removeParam(ps: List[PolyParam]) = + ps.filterNot(p => p.binder.eq(poly) && p.paramNum == idx) + def replaceParam(tp: Type, atPoly: PolyType, atIdx: Int) = tp match { + case bounds: TypeBounds => + val bounds1 = tp.substParam(param, replacement).asInstanceOf[TypeBounds] + if (bounds1 eq bounds) bounds + else dropParamIn(bounds1, atPoly, atIdx) + case _ => tp + } + var current = this + if (isRemovable(poly, idx)) current = remove(poly) + else { + current = updateEntry(param, replacement) + lowerLens.update(this, current, poly, idx, Nil) + upperLens.update(this, current, poly, idx, Nil) + } + current.foreachParam {(p, i) => + current = boundsLens.map(this, current, p, i, replaceParam(_, p, i)) + current = lowerLens.map(this, current, p, i, removeParam) + current = upperLens.map(this, current, p, i, removeParam) + } + current + } + } + + def remove(pt: PolyType)(implicit ctx: Context): This = + newConstraint(boundsMap.remove(pt), lowerMap.remove(pt), upperMap.remove(pt)) + + def isRemovable(pt: PolyType, removedParam: Int = -1): Boolean = { + val entries = boundsMap(pt) + var noneLeft = true + var i = paramCount(entries) + while (noneLeft && i > 0) { + i -= 1 + if (i != removedParam && isBounds(entries(i))) noneLeft = false + else typeVar(entries, i) match { + case tv: TypeVar => + if (!tv.inst.exists) noneLeft = false // need to keep line around to compute instType + case _ => + } + } + noneLeft + } + +// ---------- Exploration -------------------------------------------------------- + + def domainPolys: List[PolyType] = boundsMap.keys + + def domainParams: List[PolyParam] = + for { + (poly, entries) <- boundsMap.toList + n <- 0 until paramCount(entries) + if isBounds(entries(n)) + } yield PolyParam(poly, n) + + def forallParams(p: PolyParam => Boolean): Boolean = { + boundsMap.foreachBinding { (poly, entries) => + for (i <- 0 until paramCount(entries)) + if (isBounds(entries(i)) && !p(PolyParam(poly, i))) return false + } + true + } + + def foreachParam(p: (PolyType, Int) => Unit): Unit = + boundsMap.foreachBinding { (poly, entries) => + 0.until(poly.paramNames.length).foreach(p(poly, _)) + } + + def foreachTypeVar(op: TypeVar => Unit): Unit = + boundsMap.foreachBinding { (poly, entries) => + for (i <- 0 until paramCount(entries)) { + typeVar(entries, i) match { + case tv: TypeVar if !tv.inst.exists => op(tv) + case _ => + } + } + } + + private var myUninstVars: mutable.ArrayBuffer[TypeVar] = _ + + /** The uninstantiated typevars of this constraint */ + def uninstVars: collection.Seq[TypeVar] = { + if (myUninstVars == null) { + myUninstVars = new mutable.ArrayBuffer[TypeVar] + boundsMap.foreachBinding { (poly, entries) => + for (i <- 0 until paramCount(entries)) { + typeVar(entries, i) match { + case tv: TypeVar if !tv.inst.exists && isBounds(entries(i)) => myUninstVars += tv + case _ => + } + } + } + } + myUninstVars + } + +// ---------- Cyclic checking ------------------------------------------- + + def checkNonCyclic()(implicit ctx: Context): Unit = + domainParams.foreach(checkNonCyclic) + + private def checkNonCyclic(param: PolyParam)(implicit ctx: Context): Unit = + assert(!isLess(param, param), i"cyclic constraint involving $param in $this") + +// ---------- toText ----------------------------------------------------- + + override def toText(printer: Printer): Text = { + def entryText(tp: Type) = tp match { + case tp: TypeBounds => + tp.toText(printer) + case _ => + " := " ~ tp.toText(printer) + } + val indent = 3 + val header: Text = "Constraint(" + val uninstVarsText = " uninstVars = " ~ + Text(uninstVars map (_.toText(printer)), ", ") ~ ";" + val constrainedText = + " constrained types = " ~ Text(domainPolys map (_.toText(printer)), ", ") + val boundsText = + " bounds = " ~ { + val assocs = + for (param <- domainParams) + yield (" " * indent) ~ param.toText(printer) ~ entryText(entry(param)) + Text(assocs, "\n") + } + val orderingText = + " ordering = " ~ { + val deps = + for { + param <- domainParams + ups = minUpper(param) + if ups.nonEmpty + } + yield + (" " * indent) ~ param.toText(printer) ~ " <: " ~ + Text(ups.map(_.toText(printer)), ", ") + Text(deps, "\n") + } + Text.lines(List(header, uninstVarsText, constrainedText, boundsText, orderingText, ")")) + } +} \ No newline at end of file diff --git a/src/dotty/tools/dotc/core/TrackingConstraint.scala b/src/dotty/tools/dotc/core/TrackingConstraint.scala index 3f15f2b37cfe..fc8a083f400b 100644 --- a/src/dotty/tools/dotc/core/TrackingConstraint.scala +++ b/src/dotty/tools/dotc/core/TrackingConstraint.scala @@ -30,14 +30,14 @@ import TrackingConstraint._ * An instantiated type parameter is represented by having its instance type in * the corresponding array entry. */ -class TrackingConstraint(private val myMap: ParamInfo, - private val less: Array[BitSet], +class TrackingConstraint(private val myMap: ParamInfo, + private val less: Array[BitSet], private val params: Array[PolyParam]) extends Constraint { - + type This = TrackingConstraint - + assert(less.length == params.length) - + /** A new constraint which is derived from this constraint by adding or replacing * the entries corresponding to `pt` with `entries`. */ @@ -47,19 +47,19 @@ class TrackingConstraint(private val myMap: ParamInfo, ctx.runInfo.recordConstraintSize(result, result.myMap.size) result } - + // ----------- Basic indices -------------------------------------------------- /** The immutable array of constrained polytypes */ private val polyTypes = new Array[PolyType](myMap.size) - + /** The start positions of parameters of constrained polytypes in `params` and `less` */ private val polyStart = new Array[Int](myMap.size) - + { var idx = 0 var count = 0 - myMap.foreachBinding { (pt, _) => + myMap.foreachBinding { (pt, _) => polyTypes(idx) = pt polyStart(idx) = count count += pt.paramNames.length @@ -67,8 +67,8 @@ class TrackingConstraint(private val myMap: ParamInfo, } assert(count == params.length) } - - /** The index of given polytype `pt` in this constraint, + + /** The index of given polytype `pt` in this constraint, * or `polyTypes.length` if constraint does not contain `pt`. */ private def polyIndex(pt: PolyType): Int = { @@ -76,16 +76,16 @@ class TrackingConstraint(private val myMap: ParamInfo, while (i < polyTypes.length && (polyTypes(i) ne pt)) i += 1 i } - + /** The index of the first parameter of given polytype `pt` in this constraint */ private def polyStart(pt: PolyType): Int = this.polyStart.apply(polyIndex(pt)) - + /** The index of `param` in `params` and `less` */ private def paramIndex(param: PolyParam): Int = { assert(contains(param.binder)) polyStart(param.binder) + param.paramNum } - + /** The number of type parameters in the given entry array */ private def paramCount(entries: Array[Type]) = entries.length >> 1 @@ -98,7 +98,7 @@ class TrackingConstraint(private val myMap: ParamInfo, if (entries == null) NoType else entries(param.paramNum) } - + // ----------- Contains tests -------------------------------------------------- def contains(pt: PolyType): Boolean = polyIndex(pt) < polyTypes.length @@ -116,59 +116,59 @@ class TrackingConstraint(private val myMap: ParamInfo, } private def isBounds(tp: Type) = tp.isInstanceOf[TypeBounds] - + // ---------- Dependency handling ---------------------------------------------- - + private def upperBits(less: Array[BitSet], i: Int): BitSet = less(i) - - private def lowerBits(less: Array[BitSet], i: Int): BitSet = + + private def lowerBits(less: Array[BitSet], i: Int): BitSet = (BitSet() /: less.indices) ((bits, j) => if (less(j)(i)) bits + j else bits) - + private def minUpperBits(less: Array[BitSet], i: Int): BitSet = { val all = upperBits(less, i) all.filterNot(j => all.exists(k => less(k)(j))) } - + private def minLowerBits(less: Array[BitSet], i: Int): BitSet = { val all = lowerBits(less, i) all.filterNot(j => all.exists(k => less(j)(k))) } - + private def overParams(op: (Array[BitSet], Int) => BitSet): PolyParam => List[PolyParam] = param => op(less, paramIndex(param)).toList.map(params).filter(contains) - + val allUpper = overParams(upperBits) val allLower = overParams(lowerBits) val minUpper = overParams(minUpperBits) val minLower = overParams(minLowerBits) - + def upper(param: PolyParam): List[PolyParam] = allUpper(param) def lower(param: PolyParam): List[PolyParam] = allLower(param) - + def exclusiveLower(param: PolyParam, butNot: PolyParam): List[PolyParam] = { val excluded = lowerBits(less, paramIndex(butNot)) overParams(lowerBits(_, _) &~ excluded)(param) } - + def exclusiveUpper(param: PolyParam, butNot: PolyParam): List[PolyParam] = { val excluded = upperBits(less, paramIndex(butNot)) overParams(upperBits(_, _) &~ excluded)(param) } - + // ---------- Info related to PolyParams ------------------------------------------- - def isLess(param1: PolyParam, param2: PolyParam)(implicit ctx: Context): Boolean = + def isLess(param1: PolyParam, param2: PolyParam): Boolean = less(paramIndex(param1))(paramIndex(param2)) - def nonParamBounds(param: PolyParam): TypeBounds = + def nonParamBounds(param: PolyParam): TypeBounds = entry(param).asInstanceOf[TypeBounds] - + def fullLowerBound(param: PolyParam)(implicit ctx: Context): Type = (nonParamBounds(param).lo /: minLower(param))(_ | _) - def fullUpperBound(param: PolyParam)(implicit ctx: Context): Type = + def fullUpperBound(param: PolyParam)(implicit ctx: Context): Type = (nonParamBounds(param).hi /: minUpper(param))(_ & _) - + def fullBounds(param: PolyParam)(implicit ctx: Context): TypeBounds = nonParamBounds(param).derivedTypeBounds(fullLowerBound(param), fullUpperBound(param)) @@ -180,16 +180,16 @@ class TrackingConstraint(private val myMap: ParamInfo, if (tvar != null) tvar else NoType } } - + // ---------- Adding PolyTypes -------------------------------------------------- - + /** The bound type `tp` without dependent parameters * NoType if type consists only of dependent parameters. * @param seenFromBelow If true, `bound` is an upper bound, else a lower bound. */ - private def stripParams(tp: Type, handleParam: (PolyParam, Boolean) => Type, + private def stripParams(tp: Type, handleParam: (PolyParam, Boolean) => Type, seenFromBelow: Boolean)(implicit ctx: Context): Type = tp match { - case tp: PolyParam => + case tp: PolyParam => handleParam(tp, seenFromBelow) case tp: AndOrType if seenFromBelow == tp.isAnd => val tp1 = stripParams(tp.tp1, handleParam, seenFromBelow) @@ -200,17 +200,17 @@ class TrackingConstraint(private val myMap: ParamInfo, else tp2 case _ => tp - } - + } + /** The bound type `tp` without dependent parameters. * A top or bottom type if type consists only of dependent parameters. * @param seenFromBelow If true, `bound` is an upper bound, else a lower bound. */ - private def nonParamType(tp: Type, handleParam: (PolyParam, Boolean) => Type, - seenFromBelow: Boolean)(implicit ctx: Context): Type = + private def nonParamType(tp: Type, handleParam: (PolyParam, Boolean) => Type, + seenFromBelow: Boolean)(implicit ctx: Context): Type = stripParams(tp, handleParam, seenFromBelow) .orElse(if (seenFromBelow) defn.AnyType else defn.NothingType) - + /** The bounds of `tp1` without dependent parameters. * @pre `tp` is a TypeBounds type. */ @@ -234,7 +234,7 @@ class TrackingConstraint(private val myMap: ParamInfo, for (i <- is) { def handleParam(param: PolyParam, seenFromBelow: Boolean): Type = { def record(paramIdx: Int): Type = { - less1 = + less1 = if (seenFromBelow) updatedLess(less1, nparams + i, paramIdx) else updatedLess(less1, paramIdx, nparams + i) NoType @@ -247,9 +247,9 @@ class TrackingConstraint(private val myMap: ParamInfo, } newConstraint(myMap.updated(poly, entries1), less1, params1) } - + // ---------- Updates ------------------------------------------------------------ - + /** An updated partial order matrix that incorporates `less` and also reflects that `param` relates * to `p2` wrt <:< if `inOrder` is true, `>:>` otherwise. */ @@ -267,31 +267,31 @@ class TrackingConstraint(private val myMap: ParamInfo, result } } - + def addLess(p1: PolyParam, p2: PolyParam)(implicit ctx: Context): This = { val less1 = updatedLess(less, paramIndex(p1), paramIndex(p2)) if (less1 eq less) this else newConstraint(myMap, less1, params) } - + def updateEntry(param: PolyParam, tp: Type)(implicit ctx: Context): This = { val entries = myMap(param.binder) val entry = entries(param.paramNum) - if (entry eq tp) this + if (entry eq tp) this else { val entries1 = entries.clone entries1(param.paramNum) = tp newConstraint(myMap.updated(param.binder, entries1), less, params) - } + } } def unify(p1: PolyParam, p2: PolyParam)(implicit ctx: Context): This = { val p1Bounds = (nonParamBounds(p1) & nonParamBounds(p2)).substParam(p2, p1) updateEntry(p1, p1Bounds).replace(p2, p1) } - + def narrowBound(param: PolyParam, bound: Type, isUpper: Boolean)(implicit ctx: Context): This = { - val oldBounds @ TypeBounds(lo, hi) = nonParamBounds(param) - val newBounds = + val oldBounds @ TypeBounds(lo, hi) = nonParamBounds(param) + val newBounds = if (isUpper) oldBounds.derivedTypeBounds(lo, hi & bound) else oldBounds.derivedTypeBounds(lo | bound, hi) if (newBounds eq oldBounds) this @@ -342,8 +342,8 @@ class TrackingConstraint(private val myMap: ParamInfo, } result } - - if (param == replacement) this + + if (param == replacement) this else { assert(replacement.isValueType) val pt = param.binder @@ -355,7 +355,7 @@ class TrackingConstraint(private val myMap: ParamInfo, def remove(pt: PolyType)(implicit ctx: Context): This = { val start = polyStart(pt) val skipped = pt.paramNames.length - + def shrinkSet(bits: BitSet): BitSet = (BitSet() /: bits) ((res, i) => if (i < start) res + i @@ -364,12 +364,12 @@ class TrackingConstraint(private val myMap: ParamInfo, def shrinkArray[T: ClassTag](src: Array[T]) = { val dst = new Array[T](src.length - skipped) Array.copy(src, 0, dst, 0, start) - Array.copy(src, start + skipped, dst, start, dst.length - start) + Array.copy(src, start + skipped, dst, start, dst.length - start) dst } newConstraint( - myMap = myMap remove pt, - less = shrinkArray(less).map(shrinkSet(_)), + myMap = myMap remove pt, + less = shrinkArray(less).map(shrinkSet(_)), params = shrinkArray(params)) } @@ -438,14 +438,14 @@ class TrackingConstraint(private val myMap: ParamInfo, def checkNonCyclic()(implicit ctx: Context): Unit = for (i <- params.indices) checkNonCyclic(i) - + // ---------- toText ----------------------------------------------------- override def toText(printer: Printer): Text = { def entryText(tp: Type) = tp match { - case tp: TypeBounds => + case tp: TypeBounds => tp.toText(printer) - case _ => + case _ => " := " ~ tp.toText(printer) } val indent = 3 @@ -469,7 +469,7 @@ class TrackingConstraint(private val myMap: ParamInfo, ups = minUpper(param) if ups.nonEmpty } - yield + yield (" " * indent) ~ param.toText(printer) ~ " <: " ~ Text(ups.map(_.toText(printer)), ", ") Text(deps, "\n") diff --git a/src/dotty/tools/dotc/core/TyperState.scala b/src/dotty/tools/dotc/core/TyperState.scala index bdba128a93a9..1079af510d66 100644 --- a/src/dotty/tools/dotc/core/TyperState.scala +++ b/src/dotty/tools/dotc/core/TyperState.scala @@ -17,7 +17,8 @@ class TyperState(r: Reporter) extends DotClass with Showable { def reporter = r /** The current constraint set */ - def constraint: Constraint = new TrackingConstraint(SimpleMap.Empty, Array(), Array()) + def constraint: Constraint = + new OrderingConstraint(SimpleMap.Empty, SimpleMap.Empty, SimpleMap.Empty) def constraint_=(c: Constraint): Unit = {} /** The uninstantiated variables */ From 47238eb6c8bbc852d1ec1c55404694110f32120c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 24 Jan 2015 11:19:37 +0100 Subject: [PATCH 64/72] Some finetunigs in TypeComparer. Special cases for comparing Any (and now also Nothing) restricted to comparing TypeBounds, where they occur most often. --- src/dotty/tools/dotc/core/TypeComparer.scala | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index 6532d51bff88..1de2d7e98ddb 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -50,6 +50,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi 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 @@ -71,14 +72,16 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi if (myAnyType == null) myAnyType = AnyClass.typeRef myAnyType } + def NothingType = { + if (myNothingType == null) myNothingType = NothingClass.typeRef + myNothingType + } // Subtype testing `<:<` def topLevelSubType(tp1: Type, tp2: Type): Boolean = { if (tp2 eq NoType) return false - if ((tp2 eq tp1) || - (tp2 eq WildcardType) || - (tp2 eq AnyType) && tp1.isValueType) return true + if ((tp2 eq tp1) || (tp2 eq WildcardType)) return true try isSubType(tp1, tp2) finally if (Config.checkConstraintsSatisfiable) @@ -381,8 +384,8 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi case tp2 @ TypeBounds(lo2, hi2) => def compareTypeBounds = tp1 match { case tp1 @ TypeBounds(lo1, hi1) => - (tp2.variance > 0 && tp1.variance >= 0 || isSubType(lo2, lo1)) && - (tp2.variance < 0 && tp1.variance <= 0 || isSubType(hi1, hi2)) + (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) From cdce02dc1d9c957621165545848c1f7bc358bd6d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 24 Jan 2015 11:32:25 +0100 Subject: [PATCH 65/72] Fixes suggested by reviews from @smarter. Also, added tests trhat work now. --- src/dotty/tools/dotc/core/ConstraintHandling.scala | 2 +- src/dotty/tools/dotc/core/Skolemization.scala | 2 ++ src/dotty/tools/dotc/core/TypeComparer.scala | 4 ++-- tests/pos/caseClassInMethod.scala | 2 +- tests/pos/refinedSubtyping.scala | 3 +-- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/dotty/tools/dotc/core/ConstraintHandling.scala b/src/dotty/tools/dotc/core/ConstraintHandling.scala index 6371a176b3c2..02f5bf87f4cb 100644 --- a/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -35,7 +35,7 @@ trait ConstraintHandling { implicit val ctx: Context - def isSubType(tp1: Type, tp2: Type): Boolean + protected def isSubType(tp1: Type, tp2: Type): Boolean val state: TyperState import state.constraint diff --git a/src/dotty/tools/dotc/core/Skolemization.scala b/src/dotty/tools/dotc/core/Skolemization.scala index 2e517c90e802..8bc5c815f72c 100644 --- a/src/dotty/tools/dotc/core/Skolemization.scala +++ b/src/dotty/tools/dotc/core/Skolemization.scala @@ -7,6 +7,8 @@ import collection.mutable /** Methods to add and remove skolemtypes. * * Skolem types are generated when comparing refinements. + * A skolem type is simply a fresh singleton type that has a given type + * as underlying type. * Two skolem types are equal if they refer to the same underlying type. * To avoid unsoundness, skolem types have to be kept strictly local to the * comparison, they are not allowed to escape the lifetime of a comparison diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index 1de2d7e98ddb..db6e504d51ee 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -88,7 +88,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi assert(isSatisfiable, constraint.show) } - def isSubType(tp1: Type, tp2: Type): Boolean = ctx.traceIndented(s"isSubType ${traceInfo(tp1, tp2)}", subtyping) /*<|<*/ { + 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 { @@ -179,7 +179,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi else (tp1.name eq tp2.name) && isSameType(tp1.prefix, tp2.prefix) && - (tp1.signature == tp1.signature) && + (tp1.signature == tp2.signature) && !tp1.isInstanceOf[WithFixedSym] && !tp2.isInstanceOf[WithFixedSym] ) || diff --git a/tests/pos/caseClassInMethod.scala b/tests/pos/caseClassInMethod.scala index 958e5dd473d2..2e6484792a08 100644 --- a/tests/pos/caseClassInMethod.scala +++ b/tests/pos/caseClassInMethod.scala @@ -1,5 +1,5 @@ object t { def f = { object C; case class C(); 1 } - // pending: def g = { case class D(x: Int); object D; 2 } + def g = { case class D(x: Int); object D; 2 } def h = { case class E(y: Int = 10); 3 } } diff --git a/tests/pos/refinedSubtyping.scala b/tests/pos/refinedSubtyping.scala index 329c62314f98..e6a972e1c6a8 100644 --- a/tests/pos/refinedSubtyping.scala +++ b/tests/pos/refinedSubtyping.scala @@ -60,7 +60,6 @@ class Test3 { y = x } -/* Does not work yet: class Test4 { abstract class A { type T; val xz: Any } @@ -70,4 +69,4 @@ class Test4 { val zz: A { val xz: T } = yy; } -*/ + From 25761935463a49376bf4462d89877ccbb823951e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 24 Jan 2015 18:11:27 +0100 Subject: [PATCH 66/72] Some small polishings in TypeComparer --- src/dotty/tools/dotc/core/TypeComparer.scala | 9 ++++++--- src/dotty/tools/dotc/typer/Implicits.scala | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index db6e504d51ee..6d5d78448d27 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -223,13 +223,14 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi } compareThis case tp2: SuperType => - tp1 match { + 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 ErrorType => @@ -934,7 +935,8 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi case tp1 @ MethodType(names1, formals1) => tp2 match { case tp2 @ MethodType(names2, formals2) - if Config.newMatch && (tp1.isImplicit == tp2.isImplicit) && formals1.hasSameLengthAs(formals2) => + if Config.newMatch && tp1.signature.sameParams(tp2.signature) && + tp1.isImplicit == tp2.isImplicit => tp1.derivedMethodType( mergeNames(names1, names2, nme.syntheticParamName), (formals1 zipWithConserve formals2)(_ | _), @@ -999,7 +1001,8 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi case tp1 @ MethodType(names1, formals1) => tp2 match { case tp2 @ MethodType(names2, formals2) - if Config.newMatch && (tp1.isImplicit == tp2.isImplicit) && formals1.hasSameLengthAs(formals2) => + if Config.newMatch && tp1.signature.sameParams(tp2.signature) && + tp1.isImplicit == tp2.isImplicit => tp1.derivedMethodType( mergeNames(names1, names2, nme.syntheticParamName), (formals1 zipWithConserve formals2)(_ & _), diff --git a/src/dotty/tools/dotc/typer/Implicits.scala b/src/dotty/tools/dotc/typer/Implicits.scala index 93043610e076..24d9ebf6d8b7 100644 --- a/src/dotty/tools/dotc/typer/Implicits.scala +++ b/src/dotty/tools/dotc/typer/Implicits.scala @@ -32,7 +32,7 @@ import collection.mutable object Implicits { /** A common base class of contextual implicits and of-type implicits which - * represents as set of implicit references. + * represents a set of implicit references. */ abstract class ImplicitRefs(initctx: Context) { implicit val ctx: Context = From f0e60d30a529c0ce0ca62cc942c0904595ad33b2 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 24 Jan 2015 18:19:50 +0100 Subject: [PATCH 67/72] Replace only substitutes top-level types. See comment in OrderingConstraint#replace. --- src/dotty/tools/dotc/core/Constraint.scala | 3 +- .../tools/dotc/core/OrderingConstraint.scala | 85 ++++++++++++------- 2 files changed, 55 insertions(+), 33 deletions(-) diff --git a/src/dotty/tools/dotc/core/Constraint.scala b/src/dotty/tools/dotc/core/Constraint.scala index cedfb52eb34a..c4c92d94388f 100644 --- a/src/dotty/tools/dotc/core/Constraint.scala +++ b/src/dotty/tools/dotc/core/Constraint.scala @@ -102,8 +102,9 @@ abstract class Constraint extends Showable { def unify(p1: PolyParam, p2: PolyParam)(implicit ctx: Context): This /** A new constraint which is derived from this constraint by removing - * the type parameter `param` from the domain and replacing all occurrences + * the type parameter `param` from the domain and replacing all top-level occurrences * of the parameter elsewhere in the constraint by type `tp`. + * Occurrences nested inside a refinement or prefix are not affected. */ def replace(param: PolyParam, tp: Type)(implicit ctx: Context): This diff --git a/src/dotty/tools/dotc/core/OrderingConstraint.scala b/src/dotty/tools/dotc/core/OrderingConstraint.scala index 2deec51a8cd0..ec2a654165c7 100644 --- a/src/dotty/tools/dotc/core/OrderingConstraint.scala +++ b/src/dotty/tools/dotc/core/OrderingConstraint.scala @@ -308,28 +308,31 @@ class OrderingConstraint(private val boundsMap: ParamBounds, // ---------- Removals ------------------------------------------------------------ - /** Drop parameter `PolyParam(poly, n)` from `bounds`, - * replacing with Nothing in the lower bound and by `Any` in the upper bound. + /** A new constraint which is derived from this constraint by removing + * the type parameter `param` from the domain and replacing all top-level occurrences + * of the parameter elsewhere in the constraint by type `tp`, or a conservative + * approximation of it if that is needed to avoid cycles. + * Occurrences nested inside a refinement or prefix are not affected. + * + * The reason we need to substitute top-level occurrences of the parameter + * is to deal with situations like the following. Say we have in the constraint + * + * P <: Q & String + * Q + * + * and we replace Q with P. Then substitution gives + * + * P <: P & String + * + * this would be a cyclic constraint is therefore changed by `normalize` and + * `recombine` below to + * + * P <: String + * + * approximating the RHS occurrence of P with Any. Without the substitution we + * would not find out where we need to approximate. Occurrences of parameters + * that are not top-level are not affected. */ - private def dropParamIn(bounds: TypeBounds, poly: PolyType, n: Int)(implicit ctx: Context): TypeBounds = { - def drop(tp: Type): Type = tp match { - case tp: AndOrType => - val tp1 = drop(tp.tp1) - val tp2 = drop(tp.tp2) - if (!tp1.exists) tp2 - else if (!tp2.exists) tp1 - else tp - case PolyParam(`poly`, `n`) => NoType - case _ => tp - } - def approx(tp: Type, limit: Type): Type = { - val tp1 = drop(tp) - if (tp1.exists || !tp.exists) tp1 else limit - } - bounds.derivedTypeBounds( - approx(bounds.lo, defn.NothingType), approx(bounds.hi, defn.AnyType)) - } - def replace(param: PolyParam, tp: Type)(implicit ctx: Context): OrderingConstraint = { val replacement = tp.dealias.stripTypeVar if (param == replacement) this @@ -337,22 +340,40 @@ class OrderingConstraint(private val boundsMap: ParamBounds, assert(replacement.isValueType) val poly = param.binder val idx = param.paramNum + def removeParam(ps: List[PolyParam]) = ps.filterNot(p => p.binder.eq(poly) && p.paramNum == idx) + def replaceParam(tp: Type, atPoly: PolyType, atIdx: Int) = tp match { - case bounds: TypeBounds => - val bounds1 = tp.substParam(param, replacement).asInstanceOf[TypeBounds] - if (bounds1 eq bounds) bounds - else dropParamIn(bounds1, atPoly, atIdx) + case bounds @ TypeBounds(lo, hi) => + + def recombine(andor: AndOrType, op: (Type, Boolean) => Type, isUpper: Boolean): Type = { + val tp1 = op(andor.tp1, isUpper) + val tp2 = op(andor.tp2, isUpper) + if ((tp1 eq andor.tp1) && (tp2 eq andor.tp2)) andor + else if (andor.isAnd) tp1 & tp2 + else tp1 | tp2 + } + + def normalize(tp: Type, isUpper: Boolean): Type = tp match { + case p: PolyParam if p.binder == atPoly && p.paramNum == atIdx => + if (isUpper) defn.AnyType else defn.NothingType + case tp: AndOrType if isUpper == tp.isAnd => recombine(tp, normalize, isUpper) + case _ => tp + } + + def replaceIn(tp: Type, isUpper: Boolean): Type = tp match { + case `param` => normalize(replacement, isUpper) + case tp: AndOrType if isUpper == tp.isAnd => recombine(tp, replaceIn, isUpper) + case _ => tp + } + + bounds.derivedTypeBounds(replaceIn(lo, isUpper = false), replaceIn(hi, isUpper = true)) case _ => tp } - var current = this - if (isRemovable(poly, idx)) current = remove(poly) - else { - current = updateEntry(param, replacement) - lowerLens.update(this, current, poly, idx, Nil) - upperLens.update(this, current, poly, idx, Nil) - } + + var current = + if (isRemovable(poly, idx)) remove(poly) else updateEntry(param, replacement) current.foreachParam {(p, i) => current = boundsLens.map(this, current, p, i, replaceParam(_, p, i)) current = lowerLens.map(this, current, p, i, removeParam) From eb4d80891bcfaf9ffa402ea36f1562c5f597e479 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 24 Jan 2015 18:26:52 +0100 Subject: [PATCH 68/72] Added explanation to comment. --- src/dotty/tools/dotc/core/Constraint.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/core/Constraint.scala b/src/dotty/tools/dotc/core/Constraint.scala index c4c92d94388f..d6e1589e0eca 100644 --- a/src/dotty/tools/dotc/core/Constraint.scala +++ b/src/dotty/tools/dotc/core/Constraint.scala @@ -103,7 +103,8 @@ abstract class Constraint extends Showable { /** A new constraint which is derived from this constraint by removing * the type parameter `param` from the domain and replacing all top-level occurrences - * of the parameter elsewhere in the constraint by type `tp`. + * of the parameter elsewhere in the constraint by type `tp`, or a conservative + * approximation of it if that is needed to avoid cycles. * Occurrences nested inside a refinement or prefix are not affected. */ def replace(param: PolyParam, tp: Type)(implicit ctx: Context): This From 130e9a20755d512b47ca0fe41d8dfc3f6879cf86 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 27 Jan 2015 11:13:54 +0100 Subject: [PATCH 69/72] Use normnalized type to report type errors. Without it, we get strange error messages like found: (implicit X)Y requred: Z when the problem is really that Y is not a subtype of Z. --- .../tools/dotc/typer/ErrorReporting.scala | 2 +- tests/pending/pos/t3020.scala | 9 ----- tests/pending/pos/t3037.scala | 13 ------- tests/pending/pos/t304.scala | 5 --- tests/pending/pos/t3106.scala | 7 ---- tests/pending/pos/t3137.scala | 17 -------- tests/pending/pos/t3152.scala | 20 ---------- tests/pending/pos/t3174.scala | 14 ------- tests/pending/pos/t3177.scala | 39 ------------------- 9 files changed, 1 insertion(+), 125 deletions(-) delete mode 100644 tests/pending/pos/t3020.scala delete mode 100644 tests/pending/pos/t3037.scala delete mode 100644 tests/pending/pos/t304.scala delete mode 100644 tests/pending/pos/t3106.scala delete mode 100644 tests/pending/pos/t3137.scala delete mode 100644 tests/pending/pos/t3152.scala delete mode 100755 tests/pending/pos/t3174.scala delete mode 100644 tests/pending/pos/t3177.scala diff --git a/src/dotty/tools/dotc/typer/ErrorReporting.scala b/src/dotty/tools/dotc/typer/ErrorReporting.scala index 8e8cf58f9b5c..2ed720f83444 100644 --- a/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -96,7 +96,7 @@ object ErrorReporting { def patternConstrStr(tree: Tree): String = ??? def typeMismatch(tree: Tree, pt: Type, implicitFailure: SearchFailure = NoImplicitMatches): Tree = { - errorTree(tree, typeMismatchStr(tree.tpe, pt) + implicitFailure.postscript) + errorTree(tree, typeMismatchStr(normalize(tree.tpe, pt), pt) + implicitFailure.postscript) } /** A subtype log explaining why `found` does not conform to `expected` */ diff --git a/tests/pending/pos/t3020.scala b/tests/pending/pos/t3020.scala deleted file mode 100644 index 016563e27fb8..000000000000 --- a/tests/pending/pos/t3020.scala +++ /dev/null @@ -1,9 +0,0 @@ -object Test { - def main(args: Array[String]): Unit = { - var x = true - - ( { if (x) new scala.util.Random() } .asInstanceOf[Runnable] ) - } -} - - diff --git a/tests/pending/pos/t3037.scala b/tests/pending/pos/t3037.scala deleted file mode 100644 index b71ffe0418fa..000000000000 --- a/tests/pending/pos/t3037.scala +++ /dev/null @@ -1,13 +0,0 @@ -package test - -object A { - println(("a" match { - case "a" => 1 - case _ => "a" - }).asInstanceOf[Object]) - def foo[T](x: T) = x - var x: Int = 1 - var y: Long = 1L - x = foo(x) - y = foo(y) -} diff --git a/tests/pending/pos/t304.scala b/tests/pending/pos/t304.scala deleted file mode 100644 index 76da44157d12..000000000000 --- a/tests/pending/pos/t304.scala +++ /dev/null @@ -1,5 +0,0 @@ -object O { - def f1 = -1; - def f2 = 0-1; - def f3 = -f1; -} diff --git a/tests/pending/pos/t3106.scala b/tests/pending/pos/t3106.scala deleted file mode 100644 index a9591d0aaf9f..000000000000 --- a/tests/pending/pos/t3106.scala +++ /dev/null @@ -1,7 +0,0 @@ -class Sample[A] (val d0: ((A,A)) => A) {} - -object Sample { - implicit def apply[A] (x:A): Sample[A] = { - new Sample(p => p._1) - } -} diff --git a/tests/pending/pos/t3137.scala b/tests/pending/pos/t3137.scala deleted file mode 100644 index cb7317af013d..000000000000 --- a/tests/pending/pos/t3137.scala +++ /dev/null @@ -1,17 +0,0 @@ -trait A { - val C: Any -} - -class B extends A { - class C - object C -} - -trait AA { - type C - def C: Int => C -} - -class BB extends AA { - case class C(v: Int) -} diff --git a/tests/pending/pos/t3152.scala b/tests/pending/pos/t3152.scala deleted file mode 100644 index 3d1dcbd6f098..000000000000 --- a/tests/pending/pos/t3152.scala +++ /dev/null @@ -1,20 +0,0 @@ -trait Applicative[M[_]] - -sealed trait MA[M[_], A] { - def sequence[N[_], B](implicit a: A <:< N[B], n: Applicative[N]): N[M[B]] = sys.error("stub") - // def sequence3[N[_], B]()(implicit a: A <:< N[B], n: Applicative[N]): N[M[B]] = sys.error("stub") -} - -object test { - implicit def ListMA[A](l: List[A]): MA[List, A] = sys.error("stub") - implicit val ao: Applicative[Option] = sys.error("stub") - - /* This compiles OK: - (Nil: List[Option[Int]]).sequence3(): Option[List[Int]] - */ - - // BUG: error: immutable is not an enclosing class - // !!! No line number is reported with the error - (Nil: List[Option[Int]]).sequence: Option[List[Int]] - (List[Option[Int]]()).sequence: Option[List[Int]] -} diff --git a/tests/pending/pos/t3174.scala b/tests/pending/pos/t3174.scala deleted file mode 100755 index 8d9b2578d075..000000000000 --- a/tests/pending/pos/t3174.scala +++ /dev/null @@ -1,14 +0,0 @@ -object test { - def method(): Unit = { - class Foo extends AnyRef { - object Color { - object Blue - } - - class Board { - val grid = Color.Blue - } - } - new Foo - } - } diff --git a/tests/pending/pos/t3177.scala b/tests/pending/pos/t3177.scala deleted file mode 100644 index 12dfce6eea03..000000000000 --- a/tests/pending/pos/t3177.scala +++ /dev/null @@ -1,39 +0,0 @@ -trait InvariantFunctor[F[_]] { - def xmap[A, B](ma: F[A], f: A => B, g: B => A): F[B] -} - -object InvariantFunctor { - import Endo._ - - implicit val EndoInvariantFunctor: InvariantFunctor[Endo] = new InvariantFunctor[Endo] { - def xmap[A, B](ma: Endo[A], f: A => B, g: B => A): Endo[B] = (b: B) => f(ma(g(b))) - } - - // The definition about fails with: - // anon-type.scala:9: error: not found: value b - // def xmap[A, B](ma: Endo[A], f: A => B, g: B => A): Endo[B] = (b: B) => f(ma(g(b))) - // ^ - // anon-type.scala:8: error: not found: type $anon - // implicit val EndoInvariantFunctor = new InvariantFunctor[Endo] { - // ^ - - - // These both work: - // implicit val EndoInvariantFunctorAscribed: InvariantFunctor[Endo] = new InvariantFunctor[Endo] { - // def xmap[A, B](ma: Endo[A], f: A => B, g: B => A): Endo[B] = (b: B) => f(ma(g(b))) - // } - // - // implicit val EndoInvariantFunctorStubbed = new InvariantFunctor[Endo] { - // def xmap[A, B](ma: Endo[A], f: A => B, g: B => A): Endo[B] = error("stub") - // } -} - -trait Endo[X] - -object Endo { - implicit def EndoTo[A](f: A => A): Endo[A] = new Endo[A] { - def apply(a: A) = f(a) - } - - implicit def EndoFrom[A](e: Endo[A]): A => A = e.apply(_) -} From 8a69dcc83a3ea72500d3d70584454224e49512e1 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 27 Jan 2015 11:22:29 +0100 Subject: [PATCH 70/72] Handle Apply projections also in lower compared type. t3152 shows that Apply projections can also appear in the lower of two compared types. This patch handles them. It is necessary, but not sufficient, to fix t3152. --- src/dotty/tools/dotc/core/TypeComparer.scala | 46 ++++++++++++-------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index 6d5d78448d27..9d6acee71496 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -140,21 +140,14 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi private def firstTry(tp1: Type, tp2: Type): Boolean = tp2 match { case tp2: NamedType => - def compareHKOrAlias(info1: Type) = - tp2.name == tpnme.Apply && { - val lambda2 = tp2.prefix.LambdaClass(forcing = true) - lambda2.exists && !tp1.isLambda && - tp1.testLifted(lambda2.typeParams, isSubType(_, tp2.prefix)) - } || { - tp2.info match { - case info2: TypeAlias => isSubType(tp1, info2.alias) - case _ => info1 match { - case info1: TypeAlias => isSubType(info1.alias, tp2) - case NoType => secondTry(tp1, tp2) - case _ => thirdTryNamed(tp1, tp2) - } - } + def compareAlias(info1: Type) = tp2.info match { + case info2: TypeAlias => isSubType(tp1, info2.alias) + case _ => info1 match { + case info1: TypeAlias => isSubType(info1.alias, tp2) + case NoType => secondTry(tp1, tp2) + case _ => thirdTryNamed(tp1, tp2) } + } def compareNamed = { implicit val ctx: Context = this.ctx // Dotty deviation: implicits need explicit type tp1 match { @@ -183,9 +176,12 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi !tp1.isInstanceOf[WithFixedSym] && !tp2.isInstanceOf[WithFixedSym] ) || - compareHKOrAlias(tp1.info) + compareHK(tp1, tp2, inOrder = true) || + compareHK(tp2, tp1, inOrder = false) || + compareAlias(tp1.info) case _ => - compareHKOrAlias(NoType) + compareHK(tp2, tp1, inOrder = false) || + compareAlias(NoType) } } compareNamed @@ -243,7 +239,9 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi case tp1: NamedType => tp1.info match { case info1: TypeAlias => isSubType(info1.alias, tp2) - case _ => thirdTry(tp1, 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 tp1: PolyParam => def flagNothingBound = { @@ -460,6 +458,18 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi false } + /** If `projection` is of the form T # Apply where `T` is an instance of a Lambda class, + * and `other` is not a type lambda projection, then convert `other` to a type lambda `U`, and + * continue with `T <:< U` if `inOrder` is true and `U <:< T` otherwise. + */ + def compareHK(projection: NamedType, other: Type, inOrder: Boolean) = + projection.name == tpnme.Apply && { + val lambda = projection.prefix.LambdaClass(forcing = true) + lambda.exists && !other.isLambda && + other.testLifted(lambda.typeParams, + if (inOrder) isSubType(projection.prefix, _) else isSubType(_, projection.prefix)) + } + /** Returns true iff either `tp11 <:< tp21` or `tp12 <:< tp22`, trying at the same time * to keep the constraint as wide as possible. Specifically, if * @@ -524,7 +534,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi case mbr: SingleDenotation => qualifies(mbr) case _ => mbr hasAltWith qualifies } - /*>|>*/ ctx.traceIndented(i"hasMatchingMember($tp1 . $name :? ${tp2.refinedInfo}) ${tp1.member(name).info.show} $rinfo2", subtyping) /*<|<*/ { + /*>|>*/ ctx.traceIndented(i"hasMatchingMember($base . $name :? ${tp2.refinedInfo}) ${base.member(name).info.show} $rinfo2", subtyping) /*<|<*/ { memberMatches(base member name) || tp1.isInstanceOf[SingletonType] && { // special case for situations like: From f59d1d33d6e9dbb2988f165dc9b5b03792218a4c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 27 Jan 2015 11:38:25 +0100 Subject: [PATCH 71/72] Take constrained parameter bounds into account for findMember If a PolyParam has an upper bound in the current constraint, this bound needs to be assumed when selecting members. Final patch to fix t3152. --- src/dotty/tools/dotc/core/Types.scala | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 30e394941b2c..ef8aa78b63dd 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -419,6 +419,8 @@ object Types { if mt.paramTypes.isEmpty && (tp.symbol is Stable) => mt.resultType case tp1 => tp1 }) + case tp: PolyParam => + goParam(tp) case tp: TypeProxy => go(tp.underlying) case tp: ClassInfo => @@ -460,6 +462,16 @@ object Types { // loadClassWithPrivateInnerAndSubSelf in ShowClassTests go(tp.cls.typeRef) orElse d } + def goParam(tp: PolyParam) = { + val next = tp.underlying + ctx.typerState.constraint.entry(tp) match { + case bounds: TypeBounds if bounds ne next => + ctx.typerState.ephemeral = true + go(bounds.hi) + case _ => + go(next) + } + } def goAnd(l: Type, r: Type) = go(l) & (go(r), pre) def goOr(l: Type, r: Type) = go(l) | (go(r), pre) go(this) From 57b616c1a7adc78dd46cb3ae5545e312c11e69be Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 27 Jan 2015 11:38:50 +0100 Subject: [PATCH 72/72] New tests --- tests/pos/t3020.scala | 9 +++++++++ tests/pos/t3037.scala | 13 +++++++++++++ tests/pos/t304.scala | 5 +++++ tests/pos/t3106.scala | 7 +++++++ tests/pos/t3137.scala | 17 +++++++++++++++++ tests/pos/t3152.scala | 20 ++++++++++++++++++++ tests/pos/t3174.scala | 14 ++++++++++++++ tests/pos/t3177.scala | 39 +++++++++++++++++++++++++++++++++++++++ tests/pos/t8023.scala | 9 +++++++++ tests/pos/t9004.scala | 29 +++++++++++++++++++++++++++++ 10 files changed, 162 insertions(+) create mode 100644 tests/pos/t3020.scala create mode 100644 tests/pos/t3037.scala create mode 100644 tests/pos/t304.scala create mode 100644 tests/pos/t3106.scala create mode 100644 tests/pos/t3137.scala create mode 100644 tests/pos/t3152.scala create mode 100755 tests/pos/t3174.scala create mode 100644 tests/pos/t3177.scala create mode 100644 tests/pos/t8023.scala create mode 100644 tests/pos/t9004.scala diff --git a/tests/pos/t3020.scala b/tests/pos/t3020.scala new file mode 100644 index 000000000000..016563e27fb8 --- /dev/null +++ b/tests/pos/t3020.scala @@ -0,0 +1,9 @@ +object Test { + def main(args: Array[String]): Unit = { + var x = true + + ( { if (x) new scala.util.Random() } .asInstanceOf[Runnable] ) + } +} + + diff --git a/tests/pos/t3037.scala b/tests/pos/t3037.scala new file mode 100644 index 000000000000..b71ffe0418fa --- /dev/null +++ b/tests/pos/t3037.scala @@ -0,0 +1,13 @@ +package test + +object A { + println(("a" match { + case "a" => 1 + case _ => "a" + }).asInstanceOf[Object]) + def foo[T](x: T) = x + var x: Int = 1 + var y: Long = 1L + x = foo(x) + y = foo(y) +} diff --git a/tests/pos/t304.scala b/tests/pos/t304.scala new file mode 100644 index 000000000000..76da44157d12 --- /dev/null +++ b/tests/pos/t304.scala @@ -0,0 +1,5 @@ +object O { + def f1 = -1; + def f2 = 0-1; + def f3 = -f1; +} diff --git a/tests/pos/t3106.scala b/tests/pos/t3106.scala new file mode 100644 index 000000000000..a9591d0aaf9f --- /dev/null +++ b/tests/pos/t3106.scala @@ -0,0 +1,7 @@ +class Sample[A] (val d0: ((A,A)) => A) {} + +object Sample { + implicit def apply[A] (x:A): Sample[A] = { + new Sample(p => p._1) + } +} diff --git a/tests/pos/t3137.scala b/tests/pos/t3137.scala new file mode 100644 index 000000000000..cb7317af013d --- /dev/null +++ b/tests/pos/t3137.scala @@ -0,0 +1,17 @@ +trait A { + val C: Any +} + +class B extends A { + class C + object C +} + +trait AA { + type C + def C: Int => C +} + +class BB extends AA { + case class C(v: Int) +} diff --git a/tests/pos/t3152.scala b/tests/pos/t3152.scala new file mode 100644 index 000000000000..3d1dcbd6f098 --- /dev/null +++ b/tests/pos/t3152.scala @@ -0,0 +1,20 @@ +trait Applicative[M[_]] + +sealed trait MA[M[_], A] { + def sequence[N[_], B](implicit a: A <:< N[B], n: Applicative[N]): N[M[B]] = sys.error("stub") + // def sequence3[N[_], B]()(implicit a: A <:< N[B], n: Applicative[N]): N[M[B]] = sys.error("stub") +} + +object test { + implicit def ListMA[A](l: List[A]): MA[List, A] = sys.error("stub") + implicit val ao: Applicative[Option] = sys.error("stub") + + /* This compiles OK: + (Nil: List[Option[Int]]).sequence3(): Option[List[Int]] + */ + + // BUG: error: immutable is not an enclosing class + // !!! No line number is reported with the error + (Nil: List[Option[Int]]).sequence: Option[List[Int]] + (List[Option[Int]]()).sequence: Option[List[Int]] +} diff --git a/tests/pos/t3174.scala b/tests/pos/t3174.scala new file mode 100755 index 000000000000..8d9b2578d075 --- /dev/null +++ b/tests/pos/t3174.scala @@ -0,0 +1,14 @@ +object test { + def method(): Unit = { + class Foo extends AnyRef { + object Color { + object Blue + } + + class Board { + val grid = Color.Blue + } + } + new Foo + } + } diff --git a/tests/pos/t3177.scala b/tests/pos/t3177.scala new file mode 100644 index 000000000000..12dfce6eea03 --- /dev/null +++ b/tests/pos/t3177.scala @@ -0,0 +1,39 @@ +trait InvariantFunctor[F[_]] { + def xmap[A, B](ma: F[A], f: A => B, g: B => A): F[B] +} + +object InvariantFunctor { + import Endo._ + + implicit val EndoInvariantFunctor: InvariantFunctor[Endo] = new InvariantFunctor[Endo] { + def xmap[A, B](ma: Endo[A], f: A => B, g: B => A): Endo[B] = (b: B) => f(ma(g(b))) + } + + // The definition about fails with: + // anon-type.scala:9: error: not found: value b + // def xmap[A, B](ma: Endo[A], f: A => B, g: B => A): Endo[B] = (b: B) => f(ma(g(b))) + // ^ + // anon-type.scala:8: error: not found: type $anon + // implicit val EndoInvariantFunctor = new InvariantFunctor[Endo] { + // ^ + + + // These both work: + // implicit val EndoInvariantFunctorAscribed: InvariantFunctor[Endo] = new InvariantFunctor[Endo] { + // def xmap[A, B](ma: Endo[A], f: A => B, g: B => A): Endo[B] = (b: B) => f(ma(g(b))) + // } + // + // implicit val EndoInvariantFunctorStubbed = new InvariantFunctor[Endo] { + // def xmap[A, B](ma: Endo[A], f: A => B, g: B => A): Endo[B] = error("stub") + // } +} + +trait Endo[X] + +object Endo { + implicit def EndoTo[A](f: A => A): Endo[A] = new Endo[A] { + def apply(a: A) = f(a) + } + + implicit def EndoFrom[A](e: Endo[A]): A => A = e.apply(_) +} diff --git a/tests/pos/t8023.scala b/tests/pos/t8023.scala new file mode 100644 index 000000000000..66d478abd51e --- /dev/null +++ b/tests/pos/t8023.scala @@ -0,0 +1,9 @@ +class C[K] +class D[K] + +object Test3 { + def foo = (null: Any) match { + case a: C[k] => new C[k]() // this one worked before as the info of `A` was complete + // () + } +} diff --git a/tests/pos/t9004.scala b/tests/pos/t9004.scala new file mode 100644 index 000000000000..d591bc852a7b --- /dev/null +++ b/tests/pos/t9004.scala @@ -0,0 +1,29 @@ +object Main { + trait AA[RR] { type R = RR; def r: R } + + def test1(a: AA[_]) = { + val f = () => a.r + // The tree a.r is given the type `a.R` which normalizes + // to B', where B' is a distinct symbol ("captured existential skolem") + // to substitute for the reference to an existential skolem of B. + // + // inference of the result type of the function computes the + // packed type of tree `a.r` to make sure that terms and types + // local to the body of the function don't leak into its result + // type. The captured existential skolem is considered to be local + // so it is abstracted to its upper bound, Any. + // + // However, the packedType transformation need not have even considered + // B', as it is clear that the type `a.R` is not local to the function + // body! + f: (() => a.R) + + // The workaround is to annotate the function type, rather than + // relying in inference. + val g: (() => a.R) = () => a.r + val g2 = () => a.r + + () + } + // typer debug trace: http://rawgit.com/retronym/d5aeaf8e0a4a2e6eef4b/raw/out.html +}