From 09fad430eba5f8b819671295d533ec50fb586900 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 25 Jan 2018 17:52:37 +0100 Subject: [PATCH 01/13] Make preCheckKinds convention more robust An empty type bounds as a signal to suppress checking does not work if the argument type is higher-kinded. Using NoType avoids this problem. --- compiler/src/dotty/tools/dotc/typer/Checking.scala | 9 ++++++--- compiler/src/dotty/tools/dotc/typer/Typer.scala | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index e14081207e23..55635f01fda9 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -108,12 +108,15 @@ object Checking { * in order to prevent scenarios that lead to self application of * types. Self application needs to be avoided since it can lead to stack overflows. * Test cases are neg/i2771.scala and neg/i2771b.scala. + * A NoType paramBounds is used as a sign that checking should be suppressed. */ - def preCheckKind(arg: Tree, paramBounds: TypeBounds)(implicit ctx: Context): Tree = - if (arg.tpe.widen.isRef(defn.NothingClass) || arg.tpe.hasSameKindAs(paramBounds.hi)) arg + def preCheckKind(arg: Tree, paramBounds: Type)(implicit ctx: Context): Tree = + if (arg.tpe.widen.isRef(defn.NothingClass) || + !paramBounds.exists || + arg.tpe.hasSameKindAs(paramBounds.bounds.hi)) arg else errorTree(arg, em"Type argument ${arg.tpe} has not the same kind as its bound $paramBounds") - def preCheckKinds(args: List[Tree], paramBoundss: List[TypeBounds])(implicit ctx: Context): List[Tree] = { + def preCheckKinds(args: List[Tree], paramBoundss: List[Type])(implicit ctx: Context): List[Tree] = { val args1 = args.zipWithConserve(paramBoundss)(preCheckKind) args1 ++ args.drop(paramBoundss.length) // add any arguments that do not correspond to a parameter back, diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index b01858cded27..1cdf4d9fb32c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1254,7 +1254,7 @@ class Typer extends Namer case (tparam, TypeBoundsTree(EmptyTree, EmptyTree)) => // if type argument is a wildcard, suppress kind checking since // there is no real argument. - TypeBounds.empty + NoType case (tparam, _) => tparam.paramInfo.bounds } From 6086d78a9ff3ffde2b3318ba914709196538af5c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 25 Jan 2018 17:58:39 +0100 Subject: [PATCH 02/13] Reclassify NAMEDARG Tasty tag Needs to be classified as a Num + AST node. Was misclassified before which lead to a crash in scanTrees. This needs a bump of the major version of Tasty. --- .../dotty/tools/dotc/core/tasty/TastyFormat.scala | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala index ded5386b2446..04c4c2e9d4bc 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala @@ -226,7 +226,7 @@ Standard Section: "Positions" Assoc* object TastyFormat { final val header = Array(0x5C, 0xA1, 0xAB, 0x1F) - val MajorVersion = 3 + val MajorVersion = 4 val MinorVersion = 0 /** Tags used to serialize names */ @@ -336,7 +336,6 @@ object TastyFormat { final val RECtype = 90 final val TYPEALIAS = 91 final val SINGLETONtpt = 92 - final val NAMEDARG = 93 // Cat. 4: tag Nat AST @@ -349,6 +348,7 @@ object TastyFormat { final val TYPEREFsymbol = 116 final val TYPEREF = 117 final val SELFDEF = 118 + final val NAMEDARG = 119 // Cat. 5: tag Length ... @@ -408,6 +408,15 @@ object TastyFormat { final val firstNatASTTreeTag = IDENT final val firstLengthTreeTag = PACKAGE + /** Useful for debugging */ + def isLegalTag(tag: Int) = + firstSimpleTreeTag <= tag && tag <= MACRO || + firstNatTreeTag <= tag && tag <= SYMBOLconst || + firstASTTreeTag <= tag && tag <= SINGLETONtpt || + firstNatASTTreeTag <= tag && tag <= NAMEDARG || + firstLengthTreeTag <= tag && tag <= TYPEREFin || + tag == HOLE + def isParamTag(tag: Int) = tag == PARAM || tag == TYPEPARAM def isModifierTag(tag: Int) = tag match { From 7801c578301201e3c4c9663a529b3fddec3191ea Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 25 Jan 2018 18:02:41 +0100 Subject: [PATCH 03/13] Treat wildcard args and pattern type variables the same In a type pattern, a wildcard argument and a type variable needs to be treated the same. I.e. it should make no difference if I have C[_ >: L <: H] or C[t >: L <: H] where `t` is unused. Previously, the two constructs had largely different codepaths with different things that failed and worked. This fix is necessary to mitigate the fix for #1754. The latter fix uncovered several problems with the way wildcard arguments in patterns were treated. The change also uncovered a problem in transforms: FirstTransform eliminates all type nodes wnd with it any binders bound type symbols. This means that subsequently patVars is wrong, and therefore a TreeTypeMap over a pattern will no longer duplicate pattern- bound type variables. This caused Ycheck to fail after TailRec. The fix is to keep pattern bound type variables around in an internal annotation, which is understood by patVars. --- .../src/dotty/tools/dotc/ast/TreeInfo.scala | 1 + .../src/dotty/tools/dotc/core/StdNames.scala | 1 + .../tools/dotc/transform/FirstTransform.scala | 24 +++++++++- .../src/dotty/tools/dotc/typer/Typer.scala | 44 +++++++++---------- 4 files changed, 44 insertions(+), 26 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index 8ff90e65462c..4180cd4e6a88 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -556,6 +556,7 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => val acc = new TreeAccumulator[List[Symbol]] { def apply(syms: List[Symbol], tree: Tree)(implicit ctx: Context) = tree match { case Bind(_, body) => apply(tree.symbol :: syms, body) + case Annotated(tree, id @ Ident(tpnme.BOUNDTYPE_ANNOT)) => apply(id.symbol :: syms, tree) case _ => foldOver(syms, tree) } } diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index aab111a3fdf8..fa6ff39b74aa 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -143,6 +143,7 @@ object StdNames { val INITIALIZER_PREFIX: N = "initial$" val COMPANION_MODULE_METHOD: N = "companion$module" val COMPANION_CLASS_METHOD: N = "companion$class" + val BOUNDTYPE_ANNOT: N = "$boundType$" val QUOTE: N = "'" val TYPE_QUOTE: N = "type_'" val TRAIT_SETTER_SEPARATOR: N = str.TRAIT_SETTER_SEPARATOR diff --git a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala index af46afec3a6b..4905861f4bed 100644 --- a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala +++ b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala @@ -7,6 +7,7 @@ import dotty.tools.dotc.ast.tpd import dotty.tools.dotc.core.Phases.NeedsCompanions import dotty.tools.dotc.transform.MegaPhase._ import ast.Trees._ +import ast.untpd import Flags._ import Types._ import Constants.Constant @@ -188,8 +189,27 @@ class FirstTransform extends MiniPhase with InfoTransformer { thisPhase => override def transformStats(trees: List[Tree])(implicit ctx: Context): List[Tree] = ast.Trees.flatten(reorderAndComplete(trees)(ctx.withPhase(thisPhase.next))) - private def toTypeTree(tree: Tree)(implicit ctx: Context) = - TypeTree(tree.tpe).withPos(tree.pos) + private object collectBinders extends TreeAccumulator[List[Ident]] { + def apply(annots: List[Ident], t: Tree)(implicit ctx: Context): List[Ident] = t match { + case t @ Bind(_, body) => + val annot = untpd.Ident(tpnme.BOUNDTYPE_ANNOT).withType(t.symbol.typeRef) + apply(annot :: annots, body) + case _ => + foldOver(annots, t) + } + } + + /** Replace type tree `t` of type `T` with `TypeTree(T)`, but make sure all + * binders in `t` are maintained by rewrapping binders around the type tree. + * E.g. if `t` is `C[t @ (>: L <: H)]`, replace with + * `t @ TC[_ >: L <: H]`. The body of the binder `t` is now wrong, but this does + * not matter, as we only need the info of `t`. + */ + private def toTypeTree(tree: Tree)(implicit ctx: Context) = { + val binders = collectBinders.apply(Nil, tree) + val result: Tree = TypeTree(tree.tpe).withPos(tree.pos) + (result /: binders)(Annotated(_, _)) + } override def transformOther(tree: Tree)(implicit ctx: Context) = tree match { case tree: Import => EmptyTree diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 1cdf4d9fb32c..8335c85ca55f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -568,8 +568,8 @@ class Typer extends Namer def typedTpt = checkSimpleKinded(typedType(tree.tpt)) def handlePattern: Tree = { val tpt1 = typedTpt - // special case for an abstract type that comes with a class tag if (!ctx.isAfterTyper) tpt1.tpe.<:<(pt)(ctx.addMode(Mode.GADTflexible)) + // special case for an abstract type that comes with a class tag tryWithClassTag(ascription(tpt1, isWildcard = true), pt) } cases( @@ -1012,37 +1012,29 @@ class Typer extends Namer if (!gadtCtx.gadt.bounds.contains(sym)) gadtCtx.gadt.setBounds(sym, TypeBounds.empty) - /** - replace all references to symbols associated with wildcards by their GADT bounds + /** - strip all instantiated TypeVars from pattern types. + * run/reducable.scala is a test case that shows stripping typevars is necessary. * - enter all symbols introduced by a Bind in current scope */ val indexPattern = new TreeMap { - val elimWildcardSym = new TypeMap { - def apply(t: Type) = t match { - case ref: TypeRef if ref.name == tpnme.WILDCARD && gadtCtx.gadt.bounds.contains(ref.symbol) => - gadtCtx.gadt.bounds(ref.symbol) - case TypeAlias(ref: TypeRef) if ref.name == tpnme.WILDCARD && gadtCtx.gadt.bounds.contains(ref.symbol) => - gadtCtx.gadt.bounds(ref.symbol) - case _ => - mapOver(t) - } + val stripTypeVars = new TypeMap { + def apply(t: Type) = mapOver(t) } override def transform(trt: Tree)(implicit ctx: Context) = - super.transform(trt.withType(elimWildcardSym(trt.tpe))) match { + super.transform(trt.withType(stripTypeVars(trt.tpe))) match { case b: Bind => val sym = b.symbol - if (sym.exists) { + if (sym.name != tpnme.WILDCARD) if (ctx.scope.lookup(b.name) == NoSymbol) ctx.enter(sym) else ctx.error(new DuplicateBind(b, tree), b.pos) - sym.info = elimWildcardSym(sym.info) - b - } - else { - assert(b.name == tpnme.WILDCARD) - b.body + if (!ctx.isAfterTyper) { + val bounds = ctx.gadt.bounds(sym) + if (bounds != null) sym.info = bounds } + b case t => t } - } + } def caseRest(pat: Tree)(implicit ctx: Context) = { val pat1 = indexPattern.transform(pat) @@ -1277,7 +1269,7 @@ class Typer extends Namer assignType(cpy.ByNameTypeTree(tree)(result1), result1) } - def typedTypeBoundsTree(tree: untpd.TypeBoundsTree)(implicit ctx: Context): Tree = track("typedTypeBoundsTree") { + def typedTypeBoundsTree(tree: untpd.TypeBoundsTree, pt: Type)(implicit ctx: Context): Tree = track("typedTypeBoundsTree") { val TypeBoundsTree(lo, hi) = tree val lo1 = typed(lo) val hi1 = typed(hi) @@ -1292,8 +1284,12 @@ class Typer extends Namer // with an expected type in typedTyped. The type symbol and the defining Bind node // are eliminated once the enclosing pattern has been typechecked; see `indexPattern` // in `typedCase`. - val wildcardSym = ctx.newPatternBoundSymbol(tpnme.WILDCARD, tree1.tpe, tree.pos) - untpd.Bind(tpnme.WILDCARD, tree1).withType(wildcardSym.typeRef) + //val ptt = if (lo.isEmpty && hi.isEmpty) pt else + if (ctx.isAfterTyper) tree1 + else { + val wildcardSym = ctx.newPatternBoundSymbol(tpnme.WILDCARD, tree1.tpe & pt, tree.pos) + untpd.Bind(tpnme.WILDCARD, tree1).withType(wildcardSym.typeRef) + } } else tree1 } @@ -1762,7 +1758,7 @@ class Typer extends Namer case tree: untpd.AppliedTypeTree => typedAppliedTypeTree(tree) case tree: untpd.LambdaTypeTree => typedLambdaTypeTree(tree)(localContext(tree, NoSymbol).setNewScope) case tree: untpd.ByNameTypeTree => typedByNameTypeTree(tree) - case tree: untpd.TypeBoundsTree => typedTypeBoundsTree(tree) + case tree: untpd.TypeBoundsTree => typedTypeBoundsTree(tree, pt) case tree: untpd.Alternative => typedAlternative(tree, pt) case tree: untpd.PackageDef => typedPackageDef(tree) case tree: untpd.Annotated => typedAnnotated(tree, pt) From 4f245316d1aefa8685989d50502a660e86927078 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 25 Jan 2018 18:09:49 +0100 Subject: [PATCH 04/13] Fix #1754: Don't narrow GADTs to lower bounds neg/i1754.scala succeeded because a GADT bound for `A` in `A <: B` was narrowed to the lower bound of `B` (which was nothing) instead of `B` itself. Fixing this uncovered several other problems that were hidden by the overly aggressive narrowing "feature". --- .../dotty/tools/dotc/core/TypeComparer.scala | 22 +++++++++-------- .../dotty/tools/dotc/CompilationTests.scala | 1 + tests/neg-custom-args/i1754.scala | 9 +++++++ tests/neg/boundspropagation.scala | 2 +- tests/pos/boundspropagation.scala | 5 +++- tests/pos/i1754.scala | 24 +++++++++++++++++++ 6 files changed, 51 insertions(+), 12 deletions(-) create mode 100644 tests/neg-custom-args/i1754.scala create mode 100644 tests/pos/i1754.scala diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index e41e93cd1250..67759d0db942 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -8,7 +8,7 @@ import StdNames.{nme, tpnme} import collection.mutable import util.{Stats, DotClass} import config.Config -import config.Printers.{typr, constr, subtyping, noPrinter} +import config.Printers.{typr, constr, subtyping, gadts, noPrinter} import TypeErasure.{erasedLub, erasedGlb} import TypeApplications._ import scala.util.control.NonFatal @@ -383,9 +383,9 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { narrowGADTBounds(tp2, tp1, isUpper = false)) && GADTusage(tp2.symbol) } - ((frozenConstraint || !isCappable(tp1)) && isSubType(tp1, lo2) || - compareGADT || - fourthTry(tp1, tp2)) + val tryLowerFirst = frozenConstraint || !isCappable(tp1) + if (tryLowerFirst) isSubType(tp1, lo2) || compareGADT || fourthTry(tp1, tp2) + else compareGADT || fourthTry(tp1, tp2) || isSubType(tp1, lo2) case _ => val cls2 = tp2.symbol @@ -1076,13 +1076,15 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { case _ => proto.isMatchedBy(tp) } - /** Can type `tp` be constrained from above by adding a constraint to - * a typevar that it refers to? In that case we have to be careful not - * to approximate with the lower bound of a type in `thirdTry`. Instead, - * we should first unroll `tp1` until we hit the type variable and bind the - * type variable with (the corresponding type in) `tp2` instead. + /** Can type `tp` be constrained from above, either by adding a constraint to + * a typevar that it refers to, or by narrowing a GADT bound? In that case we have + * to be careful not to approximate with the lower bound of a type in `thirdTry`. + * Instead, we should first unroll `tp1` until we hit the type variable and bind the + * type variable with (the corresponding type in) `tp2` instead. Or, in the + * case of a GADT bounded typeref, we should narrow with `tp2` instead of its lower bound. */ private def isCappable(tp: Type): Boolean = tp match { + case tp: TypeRef => ctx.gadt.bounds.contains(tp.symbol) case tp: TypeParamRef => constraint contains tp case tp: TypeProxy => isCappable(tp.underlying) case tp: AndOrType => isCappable(tp.tp1) || isCappable(tp.tp2) @@ -1096,7 +1098,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { private def narrowGADTBounds(tr: NamedType, bound: Type, isUpper: Boolean): Boolean = ctx.mode.is(Mode.GADTflexible) && !frozenConstraint && { val tparam = tr.symbol - typr.println(i"narrow gadt bound of $tparam: ${tparam.info} from ${if (isUpper) "above" else "below"} to $bound ${bound.isRef(tparam)}") + gadts.println(i"narrow gadt bound of $tparam: ${tparam.info} from ${if (isUpper) "above" else "below"} to $bound ${bound.toString} ${bound.isRef(tparam)}") if (bound.isRef(tparam)) false else { val oldBounds = ctx.gadt.bounds(tparam) diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index eb19debfa896..8e3c911fe9ce 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -175,6 +175,7 @@ class CompilationTests extends ParallelTesting { compileFilesInDir("../tests/neg", defaultOptions) + compileFilesInDir("../tests/neg-tailcall", defaultOptions) + compileFilesInDir("../tests/neg-no-optimise", defaultOptions) + + compileFile("../tests/neg-custom-args/i1754.scala", allowDeepSubtypes) + compileFile("../tests/neg-custom-args/i3246.scala", scala2Mode) + compileFile("../tests/neg-custom-args/i3627.scala", allowDeepSubtypes) + compileFile("../tests/neg-custom-args/typers.scala", allowDoubleBindings) + diff --git a/tests/neg-custom-args/i1754.scala b/tests/neg-custom-args/i1754.scala new file mode 100644 index 000000000000..68f8f2edecb6 --- /dev/null +++ b/tests/neg-custom-args/i1754.scala @@ -0,0 +1,9 @@ +case class One[T](fst: T) + +object Test { + def bad[T](e: One[T]) = e match { + case foo: One[a] => + val t: T = e.fst + val nok: Nothing = t // error + } +} diff --git a/tests/neg/boundspropagation.scala b/tests/neg/boundspropagation.scala index 0fc36e8a3d9e..63a0d1c359ba 100644 --- a/tests/neg/boundspropagation.scala +++ b/tests/neg/boundspropagation.scala @@ -33,7 +33,7 @@ object test4 { class Tree[-S, -T >: Option[S]] def g(x: Any): Tree[_, _ <: Option[N]] = x match { - case y: Tree[_, _] => y // works now (because of capture conversion?) + case y: Tree[_, _] => y // error -- used to work (because of capture conversion?) } } } diff --git a/tests/pos/boundspropagation.scala b/tests/pos/boundspropagation.scala index 8be3ff1cda9f..78366c3a1196 100644 --- a/tests/pos/boundspropagation.scala +++ b/tests/pos/boundspropagation.scala @@ -17,12 +17,15 @@ object test1 { } } } + +/** Does not work: object test2 { class Tree[S, T <: S] class Base { def g(x: Any): Tree[_, _ <: Int] = x match { - case y: Tree[Int @unchecked, _] => y + case y: Tree[Int @unchecked, t] => y } } } +*/ diff --git a/tests/pos/i1754.scala b/tests/pos/i1754.scala new file mode 100644 index 000000000000..bb0da32671d3 --- /dev/null +++ b/tests/pos/i1754.scala @@ -0,0 +1,24 @@ +object Test { + import java.util.{ concurrent => juc } + import scala.collection.concurrent + import scala.collection.convert.Wrappers._ + + /** + * Implicitly converts a Java ConcurrentMap to a Scala mutable ConcurrentMap. + * The returned Scala ConcurrentMap is backed by the provided Java + * ConcurrentMap and any side-effects of using it via the Scala interface will + * be visible via the Java interface and vice versa. + * + * If the Java ConcurrentMap was previously obtained from an implicit or + * explicit call of `asConcurrentMap(scala.collection.mutable.ConcurrentMap)` + * then the original Scala ConcurrentMap will be returned. + * + * @param m The ConcurrentMap to be converted. + * @return A Scala mutable ConcurrentMap view of the argument. + */ + implicit def mapAsScalaConcurrentMap[A, B](m: juc.ConcurrentMap[A, B]): concurrent.Map[A, B] = m match { + case null => null + case cmw: ConcurrentMapWrapper[_, _] => cmw.underlying + case _ => new JConcurrentMapWrapper(m) + } +} From 1e91c9433ffd75ea266c32be4df2b1c5f74aa720 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 25 Jan 2018 20:57:51 +0100 Subject: [PATCH 05/13] Keep track whether compared types are precise Don't narrow GADT bounds or constraint if compared types are imprecise. Narrowing GADT bounds to imprecise types is unsound. Narrowing constraints to imprecise types loses possible types. --- .../dotty/tools/dotc/core/TypeComparer.scala | 114 ++++++++++++------ 1 file changed, 77 insertions(+), 37 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 67759d0db942..529816dab436 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -30,6 +30,11 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { private[this] var needsGc = false + /** True iff a compared type `tp1` */ + private[this] var loIsPrecise = true + private[this] var hiIsPrecise = true + val newScheme = true + /** 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 @@ -111,7 +116,9 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { } } - protected def isSubType(tp1: Type, tp2: Type): Boolean = trace(s"isSubType ${traceInfo(tp1, tp2)}", subtyping) { + protected def isSubType(tp1: Type, tp2: Type): Boolean = trace(s"isSubType ${traceInfo(tp1, tp2)} $loIsPrecise $hiIsPrecise", subtyping) { + //assert(s"isSubType ${traceInfo(tp1, tp2)} $loIsPrecise $hiIsPrecise" != + // "isSubType String <:< E false true") if (tp2 eq NoType) false else if (tp1 eq tp2) true else { @@ -179,6 +186,29 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { } } + private def isSubApproxLo(tp1: Type, tp2: Type) = { + val saved = loIsPrecise + loIsPrecise = false + try isSubType(tp1, tp2) finally loIsPrecise = saved + } + + private def isSubApproxHi(tp1: Type, tp2: Type) = { + val saved = hiIsPrecise + hiIsPrecise = false + try isSubType(tp1, tp2) finally hiIsPrecise = saved + } + + private def isSubTypePart(tp1: Type, tp2: Type) = { + val savedHi = hiIsPrecise + val savedLo = loIsPrecise + hiIsPrecise = true + loIsPrecise = true + try isSubType(tp1, tp2) finally { + hiIsPrecise = savedHi + loIsPrecise = savedLo + } + } + private def firstTry(tp1: Type, tp2: Type): Boolean = tp2 match { case tp2: NamedType => def compareNamed(tp1: Type, tp2: NamedType): Boolean = { @@ -212,13 +242,13 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { if ((sym1 ne NoSymbol) && (sym1 eq sym2)) ctx.erasedTypes || sym1.isStaticOwner || - isSubType(tp1.prefix, tp2.prefix) || + isSubTypePart(tp1.prefix, tp2.prefix) || thirdTryNamed(tp1, tp2) else ( (tp1.name eq tp2.name) && tp1.isMemberRef && tp2.isMemberRef - && isSubType(tp1.prefix, tp2.prefix) + && isSubTypePart(tp1.prefix, tp2.prefix) && tp1.signature == tp2.signature && !(sym1.isClass && sym2.isClass) // class types don't subtype each other ) || @@ -274,7 +304,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { } compareSuper case AndType(tp21, tp22) => - isSubType(tp1, tp21) && isSubType(tp1, tp22) + isSubType(tp1, tp21) && isSubType(tp1, tp22) // no isSubApprox, as the two calls together maintain all information case OrType(tp21, tp22) => if (tp21.stripTypeVar eq tp22.stripTypeVar) isSubType(tp1, tp21) else secondTry(tp1, tp2) @@ -297,6 +327,13 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { secondTry(tp1, tp2) } + def testConstrain(tp1: Type, tp2: Type, isUpper: Boolean): Boolean = + !newScheme || + (if (isUpper) hiIsPrecise else loIsPrecise) || { + println(i"missing constraint $tp1 with $tp2, isUpper = $isUpper") + false + } + private def secondTry(tp1: Type, tp2: Type): Boolean = tp1 match { case tp1: NamedType => tp1.info match { @@ -319,7 +356,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { def compareTypeParamRef = ctx.mode.is(Mode.TypevarsMissContext) || isSubTypeWhenFrozen(bounds(tp1).hi, tp2) || { - if (canConstrain(tp1)) addConstraint(tp1, tp2, fromBelow = false) && flagNothingBound + if (canConstrain(tp1) && testConstrain(tp1, tp2, isUpper = true)) addConstraint(tp1, tp2, fromBelow = false) && flagNothingBound else thirdTry(tp1, tp2) } compareTypeParamRef @@ -384,8 +421,8 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { GADTusage(tp2.symbol) } val tryLowerFirst = frozenConstraint || !isCappable(tp1) - if (tryLowerFirst) isSubType(tp1, lo2) || compareGADT || fourthTry(tp1, tp2) - else compareGADT || fourthTry(tp1, tp2) || isSubType(tp1, lo2) + if (tryLowerFirst) isSubApproxHi(tp1, lo2) || compareGADT || fourthTry(tp1, tp2) + else compareGADT || fourthTry(tp1, tp2) || isSubApproxHi(tp1, lo2) case _ => val cls2 = tp2.symbol @@ -398,7 +435,8 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { if (cls2.is(JavaDefined)) // If `cls2` is parameterized, we are seeing a raw type, so we need to compare only the symbol return base.typeSymbol == cls2 - if (base ne tp1) return isSubType(base, tp2) + if (base ne tp1) + return if (tp1.isRef(cls2)) isSubType(base, tp2) else isSubApproxLo(base, tp2) } if (cls2 == defn.SingletonClass && tp1.isStable) return true } @@ -425,7 +463,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { if (frozenConstraint) isSubType(tp1, bounds(tp2).lo) else isSubTypeWhenFrozen(tp1, tp2) alwaysTrue || { - if (canConstrain(tp2)) addConstraint(tp2, tp1.widenExpr, fromBelow = true) + if (canConstrain(tp2) && testConstrain(tp2, tp1.widenExpr, isUpper = false)) addConstraint(tp2, tp1.widenExpr, fromBelow = true) else fourthTry(tp1, tp2) } } @@ -486,14 +524,14 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { def boundsOK = ctx.scala2Mode || tp1.typeParams.corresponds(tp2.typeParams)((tparam1, tparam2) => - isSubType(tparam2.paramInfo.subst(tp2, tp1), tparam1.paramInfo)) + isSubTypePart(tparam2.paramInfo.subst(tp2, tp1), tparam1.paramInfo)) val saved = comparedTypeLambdas comparedTypeLambdas += tp1 comparedTypeLambdas += tp2 try variancesConform(tp1.typeParams, tp2.typeParams) && boundsOK && - isSubType(tp1.resType, tp2.resType.subst(tp2, tp1)) + isSubTypePart(tp1.resType, tp2.resType.subst(tp2, tp1)) finally comparedTypeLambdas = saved case _ => if (tp1.isHK) { @@ -540,7 +578,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { (tp1.signature consistentParams tp2.signature) && matchingParams(tp1, tp2) && (!tp2.isImplicitMethod || tp1.isImplicitMethod) && - isSubType(tp1.resultType, tp2.resultType.subst(tp2, tp1)) + isSubTypePart(tp1.resultType, tp2.resultType.subst(tp2, tp1)) case _ => false } @@ -553,15 +591,15 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { // 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) + case tp1 @ MethodType(Nil) => isSubTypePart(tp1.resultType, restpe2) + case _ => isSubTypePart(tp1.widenExpr, restpe2) } compareExpr case tp2 @ TypeBounds(lo2, hi2) => def compareTypeBounds = tp1 match { case tp1 @ TypeBounds(lo1, hi1) => - ((lo2 eq NothingType) || isSubType(lo2, lo1)) && - ((hi2 eq AnyType) || isSubType(hi1, hi2)) + ((lo2 eq NothingType) || isSubTypePart(lo2, lo1)) && + ((hi2 eq AnyType) || isSubTypePart(hi1, hi2)) case tp1: ClassInfo => tp2 contains tp1 case _ => @@ -571,7 +609,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { case ClassInfo(pre2, cls2, _, _, _) => def compareClassInfo = tp1 match { case ClassInfo(pre1, cls1, _, _, _) => - (cls1 eq cls2) && isSubType(pre1, pre2) + (cls1 eq cls2) && isSubTypePart(pre1, pre2) case _ => false } @@ -591,7 +629,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { narrowGADTBounds(tp1, tp2, isUpper = true)) && GADTusage(tp1.symbol) } - isSubType(hi1, tp2) || compareGADT + isSubApproxLo(hi1, tp2) || compareGADT case _ => def isNullable(tp: Type): Boolean = tp.widenDealias match { case tp: TypeRef => tp.symbol.isNullableClass @@ -632,7 +670,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { case EtaExpansion(tycon1) => isSubType(tycon1, tp2) case _ => tp2 match { case tp2: HKTypeLambda => false // this case was covered in thirdTry - case _ => tp2.isHK && isSubType(tp1.resultType, tp2.appliedTo(tp1.paramRefs)) + case _ => tp2.isHK && isSubTypePart(tp1.resultType, tp2.appliedTo(tp1.paramRefs)) } } compareHKLambda @@ -659,7 +697,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { either(isSubType(tp11, tp2), isSubType(tp12, tp2)) case JavaArrayType(elem1) => def compareJavaArray = tp2 match { - case JavaArrayType(elem2) => isSubType(elem1, elem2) + case JavaArrayType(elem2) => isSubTypePart(elem1, elem2) case _ => tp2 isRef ObjectClass } compareJavaArray @@ -690,7 +728,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { case tycon1: TypeRef => tycon2.dealias match { case tycon2: TypeRef if tycon1.symbol == tycon2.symbol => - isSubType(tycon1.prefix, tycon2.prefix) && + isSubTypePart(tycon1.prefix, tycon2.prefix) && isSubArgs(args1, args2, tp1, tparams) case _ => false @@ -771,7 +809,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { * @param tyconLo The type constructor's lower approximation. */ def fallback(tyconLo: Type) = - either(fourthTry(tp1, tp2), isSubType(tp1, tyconLo.applyIfParameterized(args2))) + either(fourthTry(tp1, tp2), isSubApproxHi(tp1, tyconLo.applyIfParameterized(args2))) /** Let `tycon2bounds` be the bounds of the RHS type constructor `tycon2`. * Let `app2 = tp2` where the type constructor of `tp2` is replaced by @@ -784,9 +822,8 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { */ def compareLower(tycon2bounds: TypeBounds, tyconIsTypeRef: Boolean): Boolean = if (tycon2bounds.lo eq tycon2bounds.hi) - isSubType(tp1, - if (tyconIsTypeRef) tp2.superType - else tycon2bounds.lo.applyIfParameterized(args2)) + if (tyconIsTypeRef) isSubType(tp1, tp2.superType) + else isSubApproxHi(tp1, tycon2bounds.lo.applyIfParameterized(args2)) else fallback(tycon2bounds.lo) @@ -802,7 +839,9 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { compareLower(info2, tyconIsTypeRef = true) case info2: ClassInfo => val base = tp1.baseType(info2.cls) - if (base.exists && base.ne(tp1)) isSubType(base, tp2) + if (base.exists && base.ne(tp1)) + if (tp1.isRef(info2.cls)) isSubType(base, tp2) + else isSubApproxLo(base, tp2) else fourthTry(tp1, tp2) case _ => fourthTry(tp1, tp2) @@ -829,7 +868,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { false } canConstrain(param1) && canInstantiate || - isSubType(bounds(param1).hi.applyIfParameterized(args1), tp2) + isSubApproxLo(bounds(param1).hi.applyIfParameterized(args1), tp2) case tycon1: TypeRef if tycon1.symbol.isClass => false case tycon1: TypeProxy => @@ -863,8 +902,8 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { case arg1: TypeBounds => compareCaptured(arg1, arg2) case _ => - (v > 0 || isSubType(arg2, arg1)) && - (v < 0 || isSubType(arg1, arg2)) + (v > 0 || isSubTypePart(arg2, arg1)) && + (v < 0 || isSubTypePart(arg1, arg2)) } } @@ -967,7 +1006,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { if (isCovered(tp1) && isCovered(tp2)) { //println(s"useless subtype: $tp1 <:< $tp2") false - } else isSubType(tp1, tp2) + } else isSubApproxLo(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`? @@ -996,7 +1035,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { tp2.refinedInfo match { case rinfo2: TypeBounds => val ref1 = tp1.widenExpr.select(name) - isSubType(rinfo2.lo, ref1) && isSubType(ref1, rinfo2.hi) + isSubTypePart(rinfo2.lo, ref1) && isSubType(ref1, rinfo2.hi) case _ => false } @@ -1004,7 +1043,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { } def qualifies(m: SingleDenotation) = - isSubType(m.info, rinfo2) || matchAbstractTypeMember(m.info) + isSubTypePart(m.info, rinfo2) || matchAbstractTypeMember(m.info) tp1.member(name) match { // inlined hasAltWith for performance case mbr: SingleDenotation => qualifies(mbr) @@ -1044,7 +1083,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { */ private def isSubRefinements(tp1: RefinedType, tp2: RefinedType, limit: Type): Boolean = { def hasSubRefinement(tp1: RefinedType, refine2: Type): Boolean = { - isSubType(tp1.refinedInfo, refine2) || { + isSubTypePart(tp1.refinedInfo, refine2) || { // last effort: try to adapt variances of higher-kinded types if this is sound. // TODO: Move this to eta-expansion? val adapted2 = refine2.adaptHkVariances(tp1.parent.member(tp1.refinedName).symbol.info) @@ -1084,7 +1123,6 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { * case of a GADT bounded typeref, we should narrow with `tp2` instead of its lower bound. */ private def isCappable(tp: Type): Boolean = tp match { - case tp: TypeRef => ctx.gadt.bounds.contains(tp.symbol) case tp: TypeParamRef => constraint contains tp case tp: TypeProxy => isCappable(tp.underlying) case tp: AndOrType => isCappable(tp.tp1) || isCappable(tp.tp2) @@ -1095,8 +1133,9 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { * `bound` as an upper or lower bound (which depends on `isUpper`). * Test that the resulting bounds are still satisfiable. */ - private def narrowGADTBounds(tr: NamedType, bound: Type, isUpper: Boolean): Boolean = - ctx.mode.is(Mode.GADTflexible) && !frozenConstraint && { + private def narrowGADTBounds(tr: NamedType, bound: Type, isUpper: Boolean): Boolean = { + val boundIsPrecise = if (isUpper) hiIsPrecise else loIsPrecise + ctx.mode.is(Mode.GADTflexible) && !frozenConstraint && boundIsPrecise && { val tparam = tr.symbol gadts.println(i"narrow gadt bound of $tparam: ${tparam.info} from ${if (isUpper) "above" else "below"} to $bound ${bound.toString} ${bound.isRef(tparam)}") if (bound.isRef(tparam)) false @@ -1105,10 +1144,11 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { val newBounds = if (isUpper) TypeBounds(oldBounds.lo, oldBounds.hi & bound) else TypeBounds(oldBounds.lo | bound, oldBounds.hi) - isSubType(newBounds.lo, newBounds.hi) && + isSubTypePart(newBounds.lo, newBounds.hi) && { ctx.gadt.setBounds(tparam, newBounds); true } } } + } // Tests around `matches` From 2425aaf61a351457270e7239cbdf93b4df8666b9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 25 Jan 2018 22:27:27 +0100 Subject: [PATCH 06/13] Handle approximation state as a parameter to isSubType --- .../dotty/tools/dotc/core/TypeComparer.scala | 1482 ++++++++--------- .../src/dotty/tools/dotc/core/Types.scala | 4 +- 2 files changed, 720 insertions(+), 766 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 529816dab436..eb445970e703 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -17,8 +17,7 @@ import reporting.trace /** Provides methods to compare types. */ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { - import TypeComparer.show - + import TypeComparer._ implicit val ctx = initctx val state = ctx.typerState @@ -26,15 +25,9 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { private[this] var pendingSubTypes: mutable.Set[(Type, Type)] = null private[this] var recCount = 0 - private[this] var monitored = false private[this] var needsGc = false - /** True iff a compared type `tp1` */ - private[this] var loIsPrecise = true - private[this] var hiIsPrecise = true - val newScheme = true - /** 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 @@ -109,777 +102,758 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { if (tp2 eq NoType) return false if ((tp2 eq tp1) || (tp2 eq WildcardType)) return true try isSubType(tp1, tp2) - finally { - monitored = false + finally if (Config.checkConstraintsSatisfiable) assert(isSatisfiable, constraint.show) - } } - protected def isSubType(tp1: Type, tp2: Type): Boolean = trace(s"isSubType ${traceInfo(tp1, tp2)} $loIsPrecise $hiIsPrecise", subtyping) { - //assert(s"isSubType ${traceInfo(tp1, tp2)} $loIsPrecise $hiIsPrecise" != - // "isSubType String <:< E false true") - if (tp2 eq NoType) false - else if (tp1 eq tp2) true - else { - val saved = constraint - val savedSuccessCount = successCount - try { - recCount = recCount + 1 - if (recCount >= Config.LogPendingSubTypesThreshold) monitored = true - val result = if (monitored) monitoredIsSubType(tp1, tp2) else firstTry(tp1, tp2) - recCount = recCount - 1 - if (!result) state.resetConstraintTo(saved) - else if (recCount == 0 && needsGc) { - state.gc() - needsGc = false - } - if (Stats.monitored) recordStatistics(result, savedSuccessCount) - result - } catch { - case NonFatal(ex) => - if (ex.isInstanceOf[AssertionError]) showGoal(tp1, tp2) - recCount -= 1 - state.resetConstraintTo(saved) - successCount = savedSuccessCount - throw ex + protected def isSubType(tp1: Type, tp2: Type): Boolean = isSubType(tp1, tp2, Precise) + + protected def isSubType(tp1: Type, tp2: Type, approx: ApproxState): Boolean = trace(s"isSubType ${traceInfo(tp1, tp2)} $approx", subtyping) { + + def monitoredIsSubType = { + if (pendingSubTypes == null) { + pendingSubTypes = new mutable.HashSet[(Type, Type)] + ctx.log(s"!!! deep subtype recursion involving ${tp1.show} <:< ${tp2.show}, constraint = ${state.constraint.show}") + ctx.log(s"!!! constraint = ${constraint.show}") + //if (ctx.settings.YnoDeepSubtypes.value) { + // new Error("deep subtype").printStackTrace() + //} + assert(!ctx.settings.YnoDeepSubtypes.value) + if (Config.traceDeepSubTypeRecursions && !this.isInstanceOf[ExplainingTypeComparer]) + ctx.log(TypeComparer.explained(implicit ctx => ctx.typeComparer.isSubType(tp1, tp2, approx))) } - } - } - - private def monitoredIsSubType(tp1: Type, tp2: Type) = { - if (pendingSubTypes == null) { - pendingSubTypes = new mutable.HashSet[(Type, Type)] - ctx.log(s"!!! deep subtype recursion involving ${tp1.show} <:< ${tp2.show}, constraint = ${state.constraint.show}") - ctx.log(s"!!! constraint = ${constraint.show}") - //if (ctx.settings.YnoDeepSubtypes.value) { - // new Error("deep subtype").printStackTrace() - //} - assert(!ctx.settings.YnoDeepSubtypes.value) - if (Config.traceDeepSubTypeRecursions && !this.isInstanceOf[ExplainingTypeComparer]) - ctx.log(TypeComparer.explained(implicit ctx => ctx.typeComparer.isSubType(tp1, tp2))) - } - // Eliminate LazyRefs before checking whether we have seen a type before - val normalize = new TypeMap { - val DerefLimit = 10 - var derefCount = 0 - def apply(t: Type) = t match { - case t: LazyRef => - // Dereference a lazyref to detect underlying matching types, but - // be careful not to get into an infinite recursion. If recursion count - // exceeds `DerefLimit`, approximate with `NoType` instead. - derefCount += 1 - if (derefCount >= DerefLimit) NoType - else try mapOver(t.ref) finally derefCount -= 1 - case _ => - mapOver(t) + // Eliminate LazyRefs before checking whether we have seen a type before + val normalize = new TypeMap { + val DerefLimit = 10 + var derefCount = 0 + def apply(t: Type) = t match { + case t: LazyRef => + // Dereference a lazyref to detect underlying matching types, but + // be careful not to get into an infinite recursion. If recursion count + // exceeds `DerefLimit`, approximate with `NoType` instead. + derefCount += 1 + if (derefCount >= DerefLimit) NoType + else try mapOver(t.ref) finally derefCount -= 1 + case _ => + mapOver(t) + } } - } - val p = (normalize(tp1), normalize(tp2)) - !pendingSubTypes(p) && { - try { - pendingSubTypes += p - firstTry(tp1, tp2) - } finally { - pendingSubTypes -= p + val p = (normalize(tp1), normalize(tp2)) + !pendingSubTypes(p) && { + try { + pendingSubTypes += p + firstTry + } finally { + pendingSubTypes -= p + } } } - } - - private def isSubApproxLo(tp1: Type, tp2: Type) = { - val saved = loIsPrecise - loIsPrecise = false - try isSubType(tp1, tp2) finally loIsPrecise = saved - } - - private def isSubApproxHi(tp1: Type, tp2: Type) = { - val saved = hiIsPrecise - hiIsPrecise = false - try isSubType(tp1, tp2) finally hiIsPrecise = saved - } - private def isSubTypePart(tp1: Type, tp2: Type) = { - val savedHi = hiIsPrecise - val savedLo = loIsPrecise - hiIsPrecise = true - loIsPrecise = true - try isSubType(tp1, tp2) finally { - hiIsPrecise = savedHi - loIsPrecise = savedLo - } - } - - private def firstTry(tp1: Type, tp2: Type): Boolean = tp2 match { - case tp2: NamedType => - def compareNamed(tp1: Type, tp2: NamedType): Boolean = { - implicit val ctx = this.ctx - tp2.info match { - case info2: TypeAlias => isSubType(tp1, info2.alias) - case _ => tp1 match { - case tp1: NamedType => - tp1.info match { - case info1: TypeAlias => - if (isSubType(info1.alias, tp2)) return true - if (tp1.prefix.isStable) return false - // If tp1.prefix is stable, the alias does contain all information about the original ref, so - // there's no need to try something else. (This is important for performance). - // To see why we cannot in general stop here, consider: - // - // trait C { type A } - // trait D { type A = String } - // (C & D)#A <: C#A - // - // Following the alias leads to the judgment `String <: C#A` which is false. - // However the original judgment should be true. - case _ => - } - val sym2 = tp2.symbol - var sym1 = tp1.symbol - if (sym1.is(ModuleClass) && sym2.is(ModuleVal)) - // For convenience we want X$ <:< X.type - // This is safe because X$ self-type is X.type - sym1 = sym1.companionModule - if ((sym1 ne NoSymbol) && (sym1 eq sym2)) - ctx.erasedTypes || - sym1.isStaticOwner || - isSubTypePart(tp1.prefix, tp2.prefix) || - thirdTryNamed(tp1, tp2) - else - ( (tp1.name eq tp2.name) - && tp1.isMemberRef - && tp2.isMemberRef - && isSubTypePart(tp1.prefix, tp2.prefix) - && tp1.signature == tp2.signature - && !(sym1.isClass && sym2.isClass) // class types don't subtype each other - ) || - thirdTryNamed(tp1, tp2) + def firstTry: Boolean = tp2 match { + case tp2: NamedType => + def compareNamed(tp1: Type, tp2: NamedType): Boolean = { + implicit val ctx = this.ctx + tp2.info match { + case info2: TypeAlias => recur(tp1, info2.alias) + case _ => tp1 match { + case tp1: NamedType => + tp1.info match { + case info1: TypeAlias => + if (recur(info1.alias, tp2)) return true + if (tp1.prefix.isStable) return false + // If tp1.prefix is stable, the alias does contain all information about the original ref, so + // there's no need to try something else. (This is important for performance). + // To see why we cannot in general stop here, consider: + // + // trait C { type A } + // trait D { type A = String } + // (C & D)#A <: C#A + // + // Following the alias leads to the judgment `String <: C#A` which is false. + // However the original judgment should be true. + case _ => + } + val sym2 = tp2.symbol + var sym1 = tp1.symbol + if (sym1.is(ModuleClass) && sym2.is(ModuleVal)) + // For convenience we want X$ <:< X.type + // This is safe because X$ self-type is X.type + sym1 = sym1.companionModule + if ((sym1 ne NoSymbol) && (sym1 eq sym2)) + ctx.erasedTypes || + sym1.isStaticOwner || + isSubType(tp1.prefix, tp2.prefix) || + thirdTryNamed(tp2) + else + ( (tp1.name eq tp2.name) + && tp1.isMemberRef + && tp2.isMemberRef + && isSubType(tp1.prefix, tp2.prefix) + && tp1.signature == tp2.signature + && !(sym1.isClass && sym2.isClass) // class types don't subtype each other + ) || + thirdTryNamed(tp2) + case _ => + secondTry + } + } + } + compareNamed(tp1, tp2) + case tp2: ProtoType => + isMatchedByProto(tp2, tp1) + case tp2: BoundType => + tp2 == tp1 || secondTry + case tp2: TypeVar => + recur(tp1, tp2.underlying) + case tp2: WildcardType => + def compareWild = tp2.optBounds match { + case TypeBounds(_, hi) => recur(tp1, hi) + case NoType => true + } + compareWild + case tp2: LazyRef => + !tp2.evaluating && recur(tp1, tp2.ref) + case tp2: AnnotatedType => + recur(tp1, tp2.tpe) // todo: refine? + case tp2: ThisType => + def compareThis = { + val cls2 = tp2.cls + tp1 match { + case tp1: ThisType => + // We treat two prefixes A.this, B.this as equivalent if + // A's selftype derives from B and B's selftype derives from A. + val cls1 = tp1.cls + cls1.classInfo.selfType.derivesFrom(cls2) && + cls2.classInfo.selfType.derivesFrom(cls1) + case tp1: NamedType if cls2.is(Module) && cls2.eq(tp1.widen.typeSymbol) => + cls2.isStaticOwner || + recur(tp1.prefix, cls2.owner.thisType) || + secondTry case _ => - secondTry(tp1, tp2) + secondTry } } - } - compareNamed(tp1, tp2) - case tp2: ProtoType => - isMatchedByProto(tp2, tp1) - case tp2: BoundType => - tp2 == tp1 || secondTry(tp1, tp2) - case tp2: TypeVar => - isSubType(tp1, tp2.underlying) - case tp2: WildcardType => - def compareWild = tp2.optBounds match { - case TypeBounds(_, hi) => isSubType(tp1, hi) - case NoType => true - } - compareWild - case tp2: LazyRef => - !tp2.evaluating && isSubType(tp1, tp2.ref) - case tp2: AnnotatedType => - isSubType(tp1, tp2.tpe) // todo: refine? - case tp2: ThisType => - def compareThis = { - val cls2 = tp2.cls - tp1 match { - case tp1: ThisType => - // We treat two prefixes A.this, B.this as equivalent if - // A's selftype derives from B and B's selftype derives from A. - val cls1 = tp1.cls - cls1.classInfo.selfType.derivesFrom(cls2) && - cls2.classInfo.selfType.derivesFrom(cls1) - case tp1: NamedType if cls2.is(Module) && cls2.eq(tp1.widen.typeSymbol) => - cls2.isStaticOwner || - isSubType(tp1.prefix, cls2.owner.thisType) || - secondTry(tp1, tp2) + compareThis + case tp2: SuperType => + def compareSuper = tp1 match { + case tp1: SuperType => + isSubType(tp1.thistpe, tp2.thistpe) && + isSameType(tp1.supertpe, tp2.supertpe) case _ => - secondTry(tp1, tp2) + secondTry } - } - compareThis - case tp2: SuperType => - def compareSuper = tp1 match { - case tp1: SuperType => - isSubType(tp1.thistpe, tp2.thistpe) && - isSameType(tp1.supertpe, tp2.supertpe) - case _ => - secondTry(tp1, tp2) - } - compareSuper - case AndType(tp21, tp22) => - isSubType(tp1, tp21) && isSubType(tp1, tp22) // no isSubApprox, as the two calls together maintain all information - case OrType(tp21, tp22) => - if (tp21.stripTypeVar eq tp22.stripTypeVar) isSubType(tp1, tp21) - else secondTry(tp1, tp2) - case TypeErasure.ErasedValueType(tycon1, underlying2) => - def compareErasedValueType = tp1 match { - case TypeErasure.ErasedValueType(tycon2, underlying1) => - (tycon1.symbol eq tycon2.symbol) && isSameType(underlying1, underlying2) - case _ => - secondTry(tp1, tp2) - } - compareErasedValueType - case ConstantType(v2) => - tp1 match { - case ConstantType(v1) => v1.value == v2.value - case _ => secondTry(tp1, tp2) - } - case _: FlexType => - true - case _ => - secondTry(tp1, tp2) - } - - def testConstrain(tp1: Type, tp2: Type, isUpper: Boolean): Boolean = - !newScheme || - (if (isUpper) hiIsPrecise else loIsPrecise) || { - println(i"missing constraint $tp1 with $tp2, isUpper = $isUpper") - false + compareSuper + case AndType(tp21, tp22) => + recur(tp1, tp21) && recur(tp1, tp22) + case OrType(tp21, tp22) => + if (tp21.stripTypeVar eq tp22.stripTypeVar) recur(tp1, tp21) + else secondTry + case TypeErasure.ErasedValueType(tycon1, underlying2) => + def compareErasedValueType = tp1 match { + case TypeErasure.ErasedValueType(tycon2, underlying1) => + (tycon1.symbol eq tycon2.symbol) && isSameType(underlying1, underlying2) + case _ => + secondTry + } + compareErasedValueType + case ConstantType(v2) => + tp1 match { + case ConstantType(v1) => v1.value == v2.value + case _ => secondTry + } + case _: FlexType => + true + case _ => + secondTry } - private def secondTry(tp1: Type, tp2: Type): Boolean = tp1 match { - case tp1: NamedType => - tp1.info match { - case info1: TypeAlias => - if (isSubType(info1.alias, tp2)) return true - if (tp1.prefix.isStable) return false - case _ => - if (tp1 eq NothingType) return tp1 == tp2.bottomType - } - thirdTry(tp1, tp2) - case tp1: TypeParamRef => - def flagNothingBound = { - if (!frozenConstraint && tp2.isRef(defn.NothingClass) && state.isGlobalCommittable) { - def msg = s"!!! instantiated to Nothing: $tp1, constraint = ${constraint.show}" - if (Config.failOnInstantiationToNothing) assert(false, msg) - else ctx.log(msg) + def secondTry: Boolean = tp1 match { + case tp1: NamedType => + tp1.info match { + case info1: TypeAlias => + if (recur(info1.alias, tp2)) return true + if (tp1.prefix.isStable) return false + case _ => } - true - } - def compareTypeParamRef = - ctx.mode.is(Mode.TypevarsMissContext) || - isSubTypeWhenFrozen(bounds(tp1).hi, tp2) || { - if (canConstrain(tp1) && testConstrain(tp1, tp2, isUpper = true)) addConstraint(tp1, tp2, fromBelow = false) && flagNothingBound - else thirdTry(tp1, tp2) + thirdTry + case tp1: TypeParamRef => + def flagNothingBound = { + if (!frozenConstraint && tp2.isRef(defn.NothingClass) && state.isGlobalCommittable) { + def msg = s"!!! instantiated to Nothing: $tp1, constraint = ${constraint.show}" + if (Config.failOnInstantiationToNothing) assert(false, msg) + else ctx.log(msg) + } + true } - compareTypeParamRef - case tp1: ThisType => - val cls1 = tp1.cls - tp2 match { - case tp2: TermRef if cls1.is(Module) && cls1.eq(tp2.widen.typeSymbol) => - cls1.isStaticOwner || - isSubType(cls1.owner.thisType, tp2.prefix) || - thirdTry(tp1, tp2) - case _ => - thirdTry(tp1, tp2) - } - case tp1: SkolemType => - tp2 match { - case tp2: SkolemType if !ctx.phase.isTyper && tp1.info <:< tp2.info => true - case _ => thirdTry(tp1, tp2) - } - case tp1: TypeVar => - isSubType(tp1.underlying, tp2) - case tp1: WildcardType => - def compareWild = tp1.optBounds match { - case TypeBounds(lo, _) => isSubType(lo, tp2) - case _ => true - } - compareWild - case tp1: LazyRef => - // If `tp1` is in train of being evaluated, don't force it - // because that would cause an assertionError. Return false instead. - // See i859.scala for an example where we hit this case. - !tp1.evaluating && isSubType(tp1.ref, tp2) - case tp1: AnnotatedType => - isSubType(tp1.tpe, tp2) - case AndType(tp11, tp12) => - if (tp11.stripTypeVar eq tp12.stripTypeVar) isSubType(tp11, tp2) - else thirdTry(tp1, tp2) - case tp1 @ OrType(tp11, tp12) => - def joinOK = tp2.dealias match { - case tp2: AppliedType if !tp2.tycon.typeSymbol.isClass => - // If we apply the default algorithm for `A[X] | B[Y] <: C[Z]` where `C` is a - // type parameter, we will instantiate `C` to `A` and then fail when comparing - // with `B[Y]`. To do the right thing, we need to instantiate `C` to the - // common superclass of `A` and `B`. - isSubType(tp1.join, tp2) - case _ => - false - } - joinOK || isSubType(tp11, tp2) && isSubType(tp12, tp2) - case _: FlexType => - true - case _ => - thirdTry(tp1, tp2) - } - - private def thirdTryNamed(tp1: Type, tp2: NamedType): Boolean = tp2.info match { - case TypeBounds(lo2, _) => - def compareGADT: Boolean = { - val gbounds2 = ctx.gadt.bounds(tp2.symbol) - (gbounds2 != null) && - (isSubTypeWhenFrozen(tp1, gbounds2.lo) || - narrowGADTBounds(tp2, tp1, isUpper = false)) && - GADTusage(tp2.symbol) - } - val tryLowerFirst = frozenConstraint || !isCappable(tp1) - if (tryLowerFirst) isSubApproxHi(tp1, lo2) || compareGADT || fourthTry(tp1, tp2) - else compareGADT || fourthTry(tp1, tp2) || isSubApproxHi(tp1, lo2) - - case _ => - val cls2 = tp2.symbol - if (cls2.isClass) { - if (cls2.typeParams.nonEmpty && tp1.isHK) - isSubType(tp1, EtaExpansion(cls2.typeRef)) - else { - val base = tp1.baseType(cls2) - if (base.exists) { - if (cls2.is(JavaDefined)) - // If `cls2` is parameterized, we are seeing a raw type, so we need to compare only the symbol - return base.typeSymbol == cls2 - if (base ne tp1) - return if (tp1.isRef(cls2)) isSubType(base, tp2) else isSubApproxLo(base, tp2) + def compareTypeParamRef = + ctx.mode.is(Mode.TypevarsMissContext) || + isSubTypeWhenFrozen(bounds(tp1).hi, tp2) || { + if (canConstrain(tp1) && (approx & HiApprox) == 0) + addConstraint(tp1, tp2, fromBelow = false) && flagNothingBound + else thirdTry } - if (cls2 == defn.SingletonClass && tp1.isStable) return true + compareTypeParamRef + case tp1: ThisType => + val cls1 = tp1.cls + tp2 match { + case tp2: TermRef if cls1.is(Module) && cls1.eq(tp2.widen.typeSymbol) => + cls1.isStaticOwner || + recur(cls1.owner.thisType, tp2.prefix) || + thirdTry + case _ => + thirdTry } - } - fourthTry(tp1, tp2) - } + case tp1: SkolemType => + tp2 match { + case tp2: SkolemType if !ctx.phase.isTyper && isSubType(tp1.info, tp2.info) => true + case _ => thirdTry + } + case tp1: TypeVar => + recur(tp1.underlying, tp2) + case tp1: WildcardType => + def compareWild = tp1.optBounds match { + case TypeBounds(lo, _) => recur(lo, tp2) + case _ => true + } + compareWild + case tp1: LazyRef => + // If `tp1` is in train of being evaluated, don't force it + // because that would cause an assertionError. Return false instead. + // See i859.scala for an example where we hit this case. + !tp1.evaluating && recur(tp1.ref, tp2) + case tp1: AnnotatedType => + recur(tp1.tpe, tp2) + case AndType(tp11, tp12) => + if (tp11.stripTypeVar eq tp12.stripTypeVar) recur(tp11, tp2) + else thirdTry + case tp1 @ OrType(tp11, tp12) => + def joinOK = tp2.dealias match { + case tp2: AppliedType if !tp2.tycon.typeSymbol.isClass => + // If we apply the default algorithm for `A[X] | B[Y] <: C[Z]` where `C` is a + // type parameter, we will instantiate `C` to `A` and then fail when comparing + // with `B[Y]`. To do the right thing, we need to instantiate `C` to the + // common superclass of `A` and `B`. + recur(tp1.join, tp2) + case _ => + false + } + joinOK || recur(tp11, tp2) && recur(tp12, tp2) + case _: FlexType => + true + case _ => + thirdTry + } - private def thirdTry(tp1: Type, tp2: Type): Boolean = tp2 match { - case tp2 @ AppliedType(tycon2, args2) => - compareAppliedType2(tp1, tp2, tycon2, args2) - case tp2: NamedType => - thirdTryNamed(tp1, tp2) - case tp2: TypeParamRef => - def compareTypeParamRef = - (ctx.mode is Mode.TypevarsMissContext) || { - val alwaysTrue = - // The following condition is carefully formulated to catch all cases - // where the subtype relation is true without needing to add a constraint - // It's tricky because we might need to either appriximate tp2 by its - // lower bound or else widen tp1 and check that the result is a subtype of tp2. - // So if the constraint is not yet frozen, we do the same comparison again - // with a frozen constraint, which means that we get a chance to do the - // widening in `fourthTry` before adding to the constraint. - if (frozenConstraint) isSubType(tp1, bounds(tp2).lo) - else isSubTypeWhenFrozen(tp1, tp2) - alwaysTrue || { - if (canConstrain(tp2) && testConstrain(tp2, tp1.widenExpr, isUpper = false)) addConstraint(tp2, tp1.widenExpr, fromBelow = true) - else fourthTry(tp1, tp2) + def thirdTryNamed(tp2: NamedType): Boolean = tp2.info match { + case TypeBounds(lo2, _) => + def compareGADT: Boolean = { + val gbounds2 = ctx.gadt.bounds(tp2.symbol) + (gbounds2 != null) && + (isSubTypeWhenFrozen(tp1, gbounds2.lo) || + narrowGADTBounds(tp2, tp1, approx, isUpper = false)) && + GADTusage(tp2.symbol) } - } - compareTypeParamRef - case tp2: RefinedType => - def compareRefinedSlow: Boolean = { - val name2 = tp2.refinedName - isSubType(tp1, tp2.parent) && - (name2 == nme.WILDCARD || hasMatchingMember(name2, tp1, tp2)) - } - def compareRefined: Boolean = { - val tp1w = tp1.widen - val skipped2 = skipMatching(tp1w, tp2) - if ((skipped2 eq tp2) || !Config.fastPathForRefinedSubtype) - tp1 match { - case tp1: AndType => - // Delay calling `compareRefinedSlow` because looking up a member - // of an `AndType` can lead to a cascade of subtyping checks - // This twist is needed to make collection/generic/ParFactory.scala compile - fourthTry(tp1, tp2) || compareRefinedSlow - case tp1: HKTypeLambda => - // HKTypeLambdas do not have members. - fourthTry(tp1, tp2) - case _ => - compareRefinedSlow || fourthTry(tp1, tp2) - } - else // fast path, in particular for refinements resulting from parameterization. - isSubRefinements(tp1w.asInstanceOf[RefinedType], tp2, skipped2) && - isSubType(tp1, skipped2) - } - compareRefined - case tp2: RecType => - def compareRec = tp1.safeDealias match { - case tp1: RecType => - val rthis1 = tp1.recThis - isSubType(tp1.parent, tp2.parent.substRecThis(tp2, rthis1)) - case _ => - val tp1stable = ensureStableSingleton(tp1) - isSubType(fixRecs(tp1stable, tp1stable.widenExpr), tp2.parent.substRecThis(tp2, tp1stable)) - } - compareRec - case tp2: HKTypeLambda => - def compareTypeLambda: Boolean = tp1.stripTypeVar match { - case tp1: HKTypeLambda => - /* Don't compare bounds of lambdas under language:Scala2, or t2994 will fail. - * The issue is that, logically, bounds should compare contravariantly, - * but that would invalidate a pattern exploited in t2994: - * - * [X0 <: Number] -> Number <:< [X0] -> Any - * - * Under the new scheme, `[X0] -> Any` is NOT a kind that subsumes - * all other bounds. You'd have to write `[X0 >: Any <: Nothing] -> Any` instead. - * This might look weird, but is the only logically correct way to do it. - * - * Note: it would be nice if this could trigger a migration warning, but I - * am not sure how, since the code is buried so deep in subtyping logic. - */ - def boundsOK = - ctx.scala2Mode || - tp1.typeParams.corresponds(tp2.typeParams)((tparam1, tparam2) => - isSubTypePart(tparam2.paramInfo.subst(tp2, tp1), tparam1.paramInfo)) - val saved = comparedTypeLambdas - comparedTypeLambdas += tp1 - comparedTypeLambdas += tp2 - try - variancesConform(tp1.typeParams, tp2.typeParams) && - boundsOK && - isSubTypePart(tp1.resType, tp2.resType.subst(tp2, tp1)) - finally comparedTypeLambdas = saved - case _ => - if (tp1.isHK) { - val tparams1 = tp1.typeParams - return isSubType( - HKTypeLambda.fromParams(tparams1, tp1.appliedTo(tparams1.map(_.paramRef))), - tp2 - ) - } - else tp2 match { - case EtaExpansion(tycon2) if tycon2.symbol.isClass => - return isSubType(tp1, tycon2) - case _ => - } - fourthTry(tp1, tp2) - } - compareTypeLambda - case OrType(tp21, tp22) => - val tp1a = tp1.widenDealias - if (tp1a ne tp1) - // Follow the alias; this might avoid truncating the search space in the either below - // Note that it's safe to widen here because singleton types cannot be part of `|`. - return isSubType(tp1a, tp2) - - // Rewrite T1 <: (T211 & T212) | T22 to T1 <: (T211 | T22) and T1 <: (T212 | T22) - // and analogously for T1 <: T21 | (T221 & T222) - // `|' types to the right of <: are problematic, because - // we have to choose one constraint set or another, which might cut off - // solutions. The rewriting delays the point where we have to choose. - tp21 match { - case AndType(tp211, tp212) => - return isSubType(tp1, OrType(tp211, tp22)) && isSubType(tp1, OrType(tp212, tp22)) - case _ => - } - tp22 match { - case AndType(tp221, tp222) => - return isSubType(tp1, OrType(tp21, tp221)) && isSubType(tp1, OrType(tp21, tp222)) - case _ => - } - either(isSubType(tp1, tp21), isSubType(tp1, tp22)) || fourthTry(tp1, tp2) - case tp2: MethodOrPoly => - def compareMethod = tp1 match { - case tp1: MethodOrPoly => - (tp1.signature consistentParams tp2.signature) && - matchingParams(tp1, tp2) && - (!tp2.isImplicitMethod || tp1.isImplicitMethod) && - isSubTypePart(tp1.resultType, tp2.resultType.subst(tp2, tp1)) - case _ => - false - } - compareMethod - case tp2 @ ExprType(restpe2) => - def compareExpr = tp1 match { - // We allow ()T to be a subtype of => T. - // We need some subtype relationship between them so that e.g. - // def toString and def toString() don't clash when seen - // as members of the same type. And it seems most logical to take - // ()T <:< => T, since everything one can do with a => T one can - // also do with a ()T by automatic () insertion. - case tp1 @ MethodType(Nil) => isSubTypePart(tp1.resultType, restpe2) - case _ => isSubTypePart(tp1.widenExpr, restpe2) - } - compareExpr - case tp2 @ TypeBounds(lo2, hi2) => - def compareTypeBounds = tp1 match { - case tp1 @ TypeBounds(lo1, hi1) => - ((lo2 eq NothingType) || isSubTypePart(lo2, lo1)) && - ((hi2 eq AnyType) || isSubTypePart(hi1, hi2)) - case tp1: ClassInfo => - tp2 contains tp1 - case _ => - false - } - compareTypeBounds - case ClassInfo(pre2, cls2, _, _, _) => - def compareClassInfo = tp1 match { - case ClassInfo(pre1, cls1, _, _, _) => - (cls1 eq cls2) && isSubTypePart(pre1, pre2) - case _ => - false - } - compareClassInfo - case _ => - fourthTry(tp1, tp2) - } + val tryLowerFirst = frozenConstraint || !isCappable(tp1) + if (tryLowerFirst) isSubType(tp1, lo2, approx | HiApprox) || compareGADT || fourthTry + else compareGADT || fourthTry || isSubType(tp1, lo2, approx | HiApprox) - private def fourthTry(tp1: Type, tp2: Type): Boolean = tp1 match { - case tp1: TypeRef => - tp1.info match { - case TypeBounds(_, hi1) => - def compareGADT = { - val gbounds1 = ctx.gadt.bounds(tp1.symbol) - (gbounds1 != null) && - (isSubTypeWhenFrozen(gbounds1.hi, tp2) || - narrowGADTBounds(tp1, tp2, isUpper = true)) && - GADTusage(tp1.symbol) - } - isSubApproxLo(hi1, tp2) || compareGADT - case _ => - def isNullable(tp: Type): Boolean = tp.widenDealias match { - case tp: TypeRef => tp.symbol.isNullableClass - case tp: RefinedOrRecType => isNullable(tp.parent) - case tp: AppliedType => isNullable(tp.tycon) - case AndType(tp1, tp2) => isNullable(tp1) && isNullable(tp2) - case OrType(tp1, tp2) => isNullable(tp1) || isNullable(tp2) - case _ => false - } - val sym1 = tp1.symbol - (sym1 eq NothingClass) && tp2.isValueTypeOrLambda && !tp2.isPhantom || - (sym1 eq NullClass) && isNullable(tp2) || - (sym1 eq PhantomNothingClass) && tp1.topType == tp2.topType - } - case tp1 @ AppliedType(tycon1, args1) => - compareAppliedType1(tp1, tycon1, args1, tp2) - case tp1: SingletonType => - /** if `tp2 == p.type` and `p: q.type` then try `tp1 <:< q.type` as a last effort.*/ - def comparePaths = tp2 match { - case tp2: TermRef => - tp2.info.widenExpr.dealias match { - case tp2i: SingletonType => - isSubType(tp1, tp2i) - // see z1720.scala for a case where this can arise even in typer. - // Also, i1753.scala, to show why the dealias above is necessary. - case _ => false + case _ => + val cls2 = tp2.symbol + if (cls2.isClass) { + if (cls2.typeParams.nonEmpty && tp1.isHK) + recur(tp1, EtaExpansion(cls2.typeRef)) + else { + val base = tp1.baseType(cls2) + if (base.exists) { + if (cls2.is(JavaDefined)) + // If `cls2` is parameterized, we are seeing a raw type, so we need to compare only the symbol + return base.typeSymbol == cls2 + if (base ne tp1) + return isSubType(base, tp2, if (tp1.isRef(cls2)) approx else approx | LoApprox) + } + if (cls2 == defn.SingletonClass && tp1.isStable) return true } - case _ => - false - } - isNewSubType(tp1.underlying.widenExpr, tp2) || comparePaths - case tp1: RefinedType => - isNewSubType(tp1.parent, tp2) - case tp1: RecType => - isNewSubType(tp1.parent, tp2) - case tp1: HKTypeLambda => - def compareHKLambda = tp1 match { - case EtaExpansion(tycon1) => isSubType(tycon1, tp2) - case _ => tp2 match { - case tp2: HKTypeLambda => false // this case was covered in thirdTry - case _ => tp2.isHK && isSubTypePart(tp1.resultType, tp2.appliedTo(tp1.paramRefs)) } - } - compareHKLambda - case AndType(tp11, tp12) => - val tp2a = tp2.dealias - if (tp2a ne tp2) // Follow the alias; this might avoid truncating the search space in the either below - return isSubType(tp1, tp2a) - - // Rewrite (T111 | T112) & T12 <: T2 to (T111 & T12) <: T2 and (T112 | T12) <: T2 - // and analogously for T11 & (T121 | T122) & T12 <: T2 - // `&' types to the left of <: are problematic, because - // we have to choose one constraint set or another, which might cut off - // solutions. The rewriting delays the point where we have to choose. - tp11 match { - case OrType(tp111, tp112) => - return isSubType(AndType(tp111, tp12), tp2) && isSubType(AndType(tp112, tp12), tp2) - case _ => - } - tp12 match { - case OrType(tp121, tp122) => - return isSubType(AndType(tp11, tp121), tp2) && isSubType(AndType(tp11, tp122), tp2) - case _ => - } - either(isSubType(tp11, tp2), isSubType(tp12, tp2)) - case JavaArrayType(elem1) => - def compareJavaArray = tp2 match { - case JavaArrayType(elem2) => isSubTypePart(elem1, elem2) - case _ => tp2 isRef ObjectClass - } - compareJavaArray - case tp1: ExprType if ctx.phase.id > ctx.gettersPhase.id => - // getters might have converted T to => T, need to compensate. - isSubType(tp1.widenExpr, tp2) - case _ => - false - } - + fourthTry + } - /** Subtype test for the hk application `tp2 = tycon2[args2]`. - */ - def compareAppliedType2(tp1: Type, tp2: AppliedType, tycon2: Type, args2: List[Type]): Boolean = { - val tparams = tycon2.typeParams - if (tparams.isEmpty) return false // can happen for ill-typed programs, e.g. neg/tcpoly_overloaded.scala - - /** True if `tp1` and `tp2` have compatible type constructors and their - * corresponding arguments are subtypes relative to their variance (see `isSubArgs`). - */ - def isMatchingApply(tp1: Type): Boolean = tp1 match { - case AppliedType(tycon1, args1) => - tycon1.dealias match { - case tycon1: TypeParamRef => - (tycon1 == tycon2 || - canConstrain(tycon1) && tryInstantiate(tycon1, tycon2)) && - isSubArgs(args1, args2, tp1, tparams) - case tycon1: TypeRef => - tycon2.dealias match { - case tycon2: TypeRef if tycon1.symbol == tycon2.symbol => - isSubTypePart(tycon1.prefix, tycon2.prefix) && - isSubArgs(args1, args2, tp1, tparams) + def thirdTry: Boolean = tp2 match { + case tp2 @ AppliedType(tycon2, args2) => + compareAppliedType2(tp2, tycon2, args2) + case tp2: NamedType => + thirdTryNamed(tp2) + case tp2: TypeParamRef => + def compareTypeParamRef = + (ctx.mode is Mode.TypevarsMissContext) || { + val alwaysTrue = + // The following condition is carefully formulated to catch all cases + // where the subtype relation is true without needing to add a constraint + // It's tricky because we might need to either appriximate tp2 by its + // lower bound or else widen tp1 and check that the result is a subtype of tp2. + // So if the constraint is not yet frozen, we do the same comparison again + // with a frozen constraint, which means that we get a chance to do the + // widening in `fourthTry` before adding to the constraint. + if (frozenConstraint) isSubType(tp1, bounds(tp2).lo) + else isSubTypeWhenFrozen(tp1, tp2) + alwaysTrue || { + if (canConstrain(tp2) && (approx & LoApprox) == 0) + addConstraint(tp2, tp1.widenExpr, fromBelow = true) + else fourthTry + } + } + compareTypeParamRef + case tp2: RefinedType => + def compareRefinedSlow: Boolean = { + val name2 = tp2.refinedName + recur(tp1, tp2.parent) && + (name2 == nme.WILDCARD || hasMatchingMember(name2, tp1, tp2)) + } + def compareRefined: Boolean = { + val tp1w = tp1.widen + val skipped2 = skipMatching(tp1w, tp2) + if ((skipped2 eq tp2) || !Config.fastPathForRefinedSubtype) + tp1 match { + case tp1: AndType => + // Delay calling `compareRefinedSlow` because looking up a member + // of an `AndType` can lead to a cascade of subtyping checks + // This twist is needed to make collection/generic/ParFactory.scala compile + fourthTry || compareRefinedSlow + case tp1: HKTypeLambda => + // HKTypeLambdas do not have members. + fourthTry case _ => - false + compareRefinedSlow || fourthTry } - case tycon1: TypeVar => - isMatchingApply(tycon1.underlying) - case tycon1: AnnotatedType => - isMatchingApply(tycon1.underlying) + else // fast path, in particular for refinements resulting from parameterization. + isSubRefinements(tp1w.asInstanceOf[RefinedType], tp2, skipped2) && + recur(tp1, skipped2) + } + compareRefined + case tp2: RecType => + def compareRec = tp1.safeDealias match { + case tp1: RecType => + val rthis1 = tp1.recThis + recur(tp1.parent, tp2.parent.substRecThis(tp2, rthis1)) + case _ => + val tp1stable = ensureStableSingleton(tp1) + recur(fixRecs(tp1stable, tp1stable.widenExpr), tp2.parent.substRecThis(tp2, tp1stable)) + } + compareRec + case tp2: HKTypeLambda => + def compareTypeLambda: Boolean = tp1.stripTypeVar match { + case tp1: HKTypeLambda => + /* Don't compare bounds of lambdas under language:Scala2, or t2994 will fail. + * The issue is that, logically, bounds should compare contravariantly, + * but that would invalidate a pattern exploited in t2994: + * + * [X0 <: Number] -> Number <:< [X0] -> Any + * + * Under the new scheme, `[X0] -> Any` is NOT a kind that subsumes + * all other bounds. You'd have to write `[X0 >: Any <: Nothing] -> Any` instead. + * This might look weird, but is the only logically correct way to do it. + * + * Note: it would be nice if this could trigger a migration warning, but I + * am not sure how, since the code is buried so deep in subtyping logic. + */ + def boundsOK = + ctx.scala2Mode || + tp1.typeParams.corresponds(tp2.typeParams)((tparam1, tparam2) => + isSubType(tparam2.paramInfo.subst(tp2, tp1), tparam1.paramInfo)) + val saved = comparedTypeLambdas + comparedTypeLambdas += tp1 + comparedTypeLambdas += tp2 + try + variancesConform(tp1.typeParams, tp2.typeParams) && + boundsOK && + isSubType(tp1.resType, tp2.resType.subst(tp2, tp1)) + finally comparedTypeLambdas = saved + case _ => + if (tp1.isHK) { + val tparams1 = tp1.typeParams + return recur( + HKTypeLambda.fromParams(tparams1, tp1.appliedTo(tparams1.map(_.paramRef))), + tp2 + ) + } + else tp2 match { + case EtaExpansion(tycon2) if tycon2.symbol.isClass => + return recur(tp1, tycon2) + case _ => + } + fourthTry + } + compareTypeLambda + case OrType(tp21, tp22) => + val tp1a = tp1.widenDealias + if (tp1a ne tp1) + // Follow the alias; this might avoid truncating the search space in the either below + // Note that it's safe to widen here because singleton types cannot be part of `|`. + return recur(tp1a, tp2) + + // Rewrite T1 <: (T211 & T212) | T22 to T1 <: (T211 | T22) and T1 <: (T212 | T22) + // and analogously for T1 <: T21 | (T221 & T222) + // `|' types to the right of <: are problematic, because + // we have to choose one constraint set or another, which might cut off + // solutions. The rewriting delays the point where we have to choose. + tp21 match { + case AndType(tp211, tp212) => + return recur(tp1, OrType(tp211, tp22)) && recur(tp1, OrType(tp212, tp22)) + case _ => + } + tp22 match { + case AndType(tp221, tp222) => + return recur(tp1, OrType(tp21, tp221)) && recur(tp1, OrType(tp21, tp222)) + case _ => + } + either(recur(tp1, tp21), recur(tp1, tp22)) || fourthTry + case tp2: MethodOrPoly => + def compareMethod = tp1 match { + case tp1: MethodOrPoly => + (tp1.signature consistentParams tp2.signature) && + matchingParams(tp1, tp2) && + (!tp2.isImplicitMethod || tp1.isImplicitMethod) && + isSubType(tp1.resultType, tp2.resultType.subst(tp2, tp1)) case _ => false } + compareMethod + case tp2 @ ExprType(restpe2) => + def compareExpr = tp1 match { + // We allow ()T to be a subtype of => T. + // We need some subtype relationship between them so that e.g. + // def toString and def toString() don't clash when seen + // as members of the same type. And it seems most logical to take + // ()T <:< => T, since everything one can do with a => T one can + // also do with a ()T by automatic () insertion. + case tp1 @ MethodType(Nil) => isSubType(tp1.resultType, restpe2) + case _ => isSubType(tp1.widenExpr, restpe2) + } + compareExpr + case tp2 @ TypeBounds(lo2, hi2) => + def compareTypeBounds = tp1 match { + case tp1 @ TypeBounds(lo1, hi1) => + ((lo2 eq NothingType) || isSubType(lo2, lo1)) && + ((hi2 eq AnyType) || isSubType(hi1, hi2)) + case tp1: ClassInfo => + tp2 contains tp1 + case _ => + false + } + compareTypeBounds + case ClassInfo(pre2, cls2, _, _, _) => + def compareClassInfo = tp1 match { + case ClassInfo(pre1, cls1, _, _, _) => + (cls1 eq cls2) && isSubType(pre1, pre2) + case _ => + false + } + compareClassInfo case _ => - false + fourthTry } - /** `param2` can be instantiated to a type application prefix of the LHS - * or to a type application prefix of one of the LHS base class instances - * and the resulting type application is a supertype of `tp1`, - * or fallback to fourthTry. - */ - def canInstantiate(tycon2: TypeParamRef): Boolean = { - - /** Let - * - * `tparams_1, ..., tparams_k-1` be the type parameters of the rhs - * `tparams1_1, ..., tparams1_n-1` be the type parameters of the constructor of the lhs - * `args1_1, ..., args1_n-1` be the type arguments of the lhs - * `d = n - k` - * - * Returns `true` iff `d >= 0` and `tycon2` can be instantiated to - * - * [tparams1_d, ... tparams1_n-1] -> tycon1[args_1, ..., args_d-1, tparams_d, ... tparams_n-1] - * - * such that the resulting type application is a supertype of `tp1`. - */ - def appOK(tp1base: Type) = tp1base match { - case tp1base: AppliedType => - var tycon1 = tp1base.tycon - val args1 = tp1base.args - val tparams1all = tycon1.typeParams - val lengthDiff = tparams1all.length - tparams.length - lengthDiff >= 0 && { - val tparams1 = tparams1all.drop(lengthDiff) - variancesConform(tparams1, tparams) && { - if (lengthDiff > 0) - tycon1 = HKTypeLambda(tparams1.map(_.paramName))( - tl => tparams1.map(tparam => tl.integrate(tparams, tparam.paramInfo).bounds), - tl => tp1base.tycon.appliedTo(args1.take(lengthDiff) ++ - tparams1.indices.toList.map(tl.paramRefs(_)))) - (ctx.mode.is(Mode.TypevarsMissContext) || - tryInstantiate(tycon2, tycon1.ensureHK)) && - isSubType(tp1, tycon1.appliedTo(args2)) + def fourthTry: Boolean = tp1 match { + case tp1: TypeRef => + tp1.info match { + case TypeBounds(_, hi1) => + def compareGADT = { + val gbounds1 = ctx.gadt.bounds(tp1.symbol) + (gbounds1 != null) && + (isSubTypeWhenFrozen(gbounds1.hi, tp2) || + narrowGADTBounds(tp1, tp2, approx, isUpper = true)) && + GADTusage(tp1.symbol) + } + isSubType(hi1, tp2, approx | LoApprox) || compareGADT + case _ => + def isNullable(tp: Type): Boolean = tp.widenDealias match { + case tp: TypeRef => tp.symbol.isNullableClass + case tp: RefinedOrRecType => isNullable(tp.parent) + case tp: AppliedType => isNullable(tp.tycon) + case AndType(tp1, tp2) => isNullable(tp1) && isNullable(tp2) + case OrType(tp1, tp2) => isNullable(tp1) || isNullable(tp2) + case _ => false + } + val sym1 = tp1.symbol + (sym1 eq NothingClass) && tp2.isValueTypeOrLambda && !tp2.isPhantom || + (sym1 eq NullClass) && isNullable(tp2) || + (sym1 eq PhantomNothingClass) && tp1.topType == tp2.topType + } + case tp1 @ AppliedType(tycon1, args1) => + compareAppliedType1(tp1, tycon1, args1) + case tp1: SingletonType => + /** if `tp2 == p.type` and `p: q.type` then try `tp1 <:< q.type` as a last effort.*/ + def comparePaths = tp2 match { + case tp2: TermRef => + tp2.info.widenExpr.dealias match { + case tp2i: SingletonType => + recur(tp1, tp2i) + // see z1720.scala for a case where this can arise even in typer. + // Also, i1753.scala, to show why the dealias above is necessary. + case _ => false } + case _ => + false + } + isNewSubType(tp1.underlying.widenExpr) || comparePaths + case tp1: RefinedType => + isNewSubType(tp1.parent) + case tp1: RecType => + isNewSubType(tp1.parent) + case tp1: HKTypeLambda => + def compareHKLambda = tp1 match { + case EtaExpansion(tycon1) => recur(tycon1, tp2) + case _ => tp2 match { + case tp2: HKTypeLambda => false // this case was covered in thirdTry + case _ => tp2.isHK && isSubType(tp1.resultType, tp2.appliedTo(tp1.paramRefs)) } - case _ => false + } + compareHKLambda + case AndType(tp11, tp12) => + val tp2a = tp2.dealias + if (tp2a ne tp2) // Follow the alias; this might avoid truncating the search space in the either below + return recur(tp1, tp2a) + + // Rewrite (T111 | T112) & T12 <: T2 to (T111 & T12) <: T2 and (T112 | T12) <: T2 + // and analogously for T11 & (T121 | T122) & T12 <: T2 + // `&' types to the left of <: are problematic, because + // we have to choose one constraint set or another, which might cut off + // solutions. The rewriting delays the point where we have to choose. + tp11 match { + case OrType(tp111, tp112) => + return recur(AndType(tp111, tp12), tp2) && recur(AndType(tp112, tp12), tp2) + case _ => + } + tp12 match { + case OrType(tp121, tp122) => + return recur(AndType(tp11, tp121), tp2) && recur(AndType(tp11, tp122), tp2) + case _ => + } + either(recur(tp11, tp2), recur(tp12, tp2)) + case JavaArrayType(elem1) => + def compareJavaArray = tp2 match { + case JavaArrayType(elem2) => isSubType(elem1, elem2) + case _ => tp2 isRef ObjectClass + } + compareJavaArray + case tp1: ExprType if ctx.phase.id > ctx.gettersPhase.id => + // getters might have converted T to => T, need to compensate. + recur(tp1.widenExpr, tp2) + case _ => + false + } + + /** Subtype test for the hk application `tp2 = tycon2[args2]`. + */ + def compareAppliedType2(tp2: AppliedType, tycon2: Type, args2: List[Type]): Boolean = { + val tparams = tycon2.typeParams + if (tparams.isEmpty) return false // can happen for ill-typed programs, e.g. neg/tcpoly_overloaded.scala + + /** True if `tp1` and `tp2` have compatible type constructors and their + * corresponding arguments are subtypes relative to their variance (see `isSubArgs`). + */ + def isMatchingApply(tp1: Type): Boolean = tp1 match { + case AppliedType(tycon1, args1) => + tycon1.dealias match { + case tycon1: TypeParamRef => + (tycon1 == tycon2 || + canConstrain(tycon1) && tryInstantiate(tycon1, tycon2)) && + isSubArgs(args1, args2, tp1, tparams) + case tycon1: TypeRef => + tycon2.dealias match { + case tycon2: TypeRef if tycon1.symbol == tycon2.symbol => + isSubType(tycon1.prefix, tycon2.prefix) && + isSubArgs(args1, args2, tp1, tparams) + case _ => + false + } + case tycon1: TypeVar => + isMatchingApply(tycon1.underlying) + case tycon1: AnnotatedType => + isMatchingApply(tycon1.underlying) + case _ => + false + } + case _ => + false } - tp1.widen match { - case tp1w: AppliedType => appOK(tp1w) - case tp1w => - tp1w.typeSymbol.isClass && { - val classBounds = tycon2.classSymbols - def liftToBase(bcs: List[ClassSymbol]): Boolean = bcs match { - case bc :: bcs1 => - classBounds.exists(bc.derivesFrom) && appOK(tp1w.baseType(bc)) || - liftToBase(bcs1) + /** `param2` can be instantiated to a type application prefix of the LHS + * or to a type application prefix of one of the LHS base class instances + * and the resulting type application is a supertype of `tp1`, + * or fallback to fourthTry. + */ + def canInstantiate(tycon2: TypeParamRef): Boolean = { + + /** Let + * + * `tparams_1, ..., tparams_k-1` be the type parameters of the rhs + * `tparams1_1, ..., tparams1_n-1` be the type parameters of the constructor of the lhs + * `args1_1, ..., args1_n-1` be the type arguments of the lhs + * `d = n - k` + * + * Returns `true` iff `d >= 0` and `tycon2` can be instantiated to + * + * [tparams1_d, ... tparams1_n-1] -> tycon1[args_1, ..., args_d-1, tparams_d, ... tparams_n-1] + * + * such that the resulting type application is a supertype of `tp1`. + */ + def appOK(tp1base: Type) = tp1base match { + case tp1base: AppliedType => + var tycon1 = tp1base.tycon + val args1 = tp1base.args + val tparams1all = tycon1.typeParams + val lengthDiff = tparams1all.length - tparams.length + lengthDiff >= 0 && { + val tparams1 = tparams1all.drop(lengthDiff) + variancesConform(tparams1, tparams) && { + if (lengthDiff > 0) + tycon1 = HKTypeLambda(tparams1.map(_.paramName))( + tl => tparams1.map(tparam => tl.integrate(tparams, tparam.paramInfo).bounds), + tl => tp1base.tycon.appliedTo(args1.take(lengthDiff) ++ + tparams1.indices.toList.map(tl.paramRefs(_)))) + (ctx.mode.is(Mode.TypevarsMissContext) || + tryInstantiate(tycon2, tycon1.ensureHK)) && + recur(tp1, tycon1.appliedTo(args2)) + } + } + case _ => false + } + + tp1.widen match { + case tp1w: AppliedType => appOK(tp1w) + case tp1w => + tp1w.typeSymbol.isClass && { + val classBounds = tycon2.classSymbols + def liftToBase(bcs: List[ClassSymbol]): Boolean = bcs match { + case bc :: bcs1 => + classBounds.exists(bc.derivesFrom) && appOK(tp1w.baseType(bc)) || + liftToBase(bcs1) + case _ => + false + } + liftToBase(tp1w.baseClasses) + } || + fourthTry + } + } + + /** Fall back to comparing either with `fourthTry` or against the lower + * approximation of the rhs. + * @param tyconLo The type constructor's lower approximation. + */ + def fallback(tyconLo: Type) = + either(fourthTry, isSubType(tp1, tyconLo.applyIfParameterized(args2), approx | HiApprox)) + + /** Let `tycon2bounds` be the bounds of the RHS type constructor `tycon2`. + * Let `app2 = tp2` where the type constructor of `tp2` is replaced by + * `tycon2bounds.lo`. + * If both bounds are the same, continue with `tp1 <:< app2`. + * otherwise continue with either + * + * tp1 <:< tp2 using fourthTry (this might instantiate params in tp1) + * tp1 <:< app2 using isSubType (this might instantiate params in tp2) + */ + def compareLower(tycon2bounds: TypeBounds, tyconIsTypeRef: Boolean): Boolean = + if (tycon2bounds.lo eq tycon2bounds.hi) + if (tyconIsTypeRef) recur(tp1, tp2.superType) + else isSubType(tp1, tycon2bounds.lo.applyIfParameterized(args2), approx | HiApprox) + else + fallback(tycon2bounds.lo) + + tycon2 match { + case param2: TypeParamRef => + isMatchingApply(tp1) || + canConstrain(param2) && canInstantiate(param2) || + compareLower(bounds(param2), tyconIsTypeRef = false) + case tycon2: TypeRef => + isMatchingApply(tp1) || { + tycon2.info match { + case info2: TypeBounds => + compareLower(info2, tyconIsTypeRef = true) + case info2: ClassInfo => + val base = tp1.baseType(info2.cls) + if (base.exists && base.ne(tp1)) + isSubType(base, tp2, if (tp1.isRef(info2.cls)) approx else approx | LoApprox) + else fourthTry case _ => - false + fourthTry } - liftToBase(tp1w.baseClasses) - } || - fourthTry(tp1, tp2) + } + case _: TypeVar | _: AnnotatedType => + recur(tp1, tp2.superType) + case tycon2: AppliedType => + fallback(tycon2.lowerBound) + case _ => + false } } - /** Fall back to comparing either with `fourthTry` or against the lower - * approximation of the rhs. - * @param tyconLo The type constructor's lower approximation. - */ - def fallback(tyconLo: Type) = - either(fourthTry(tp1, tp2), isSubApproxHi(tp1, tyconLo.applyIfParameterized(args2))) - - /** Let `tycon2bounds` be the bounds of the RHS type constructor `tycon2`. - * Let `app2 = tp2` where the type constructor of `tp2` is replaced by - * `tycon2bounds.lo`. - * If both bounds are the same, continue with `tp1 <:< app2`. - * otherwise continue with either - * - * tp1 <:< tp2 using fourthTry (this might instantiate params in tp1) - * tp1 <:< app2 using isSubType (this might instantiate params in tp2) - */ - def compareLower(tycon2bounds: TypeBounds, tyconIsTypeRef: Boolean): Boolean = - if (tycon2bounds.lo eq tycon2bounds.hi) - if (tyconIsTypeRef) isSubType(tp1, tp2.superType) - else isSubApproxHi(tp1, tycon2bounds.lo.applyIfParameterized(args2)) - else - fallback(tycon2bounds.lo) - - tycon2 match { - case param2: TypeParamRef => - isMatchingApply(tp1) || - canConstrain(param2) && canInstantiate(param2) || - compareLower(bounds(param2), tyconIsTypeRef = false) - case tycon2: TypeRef => - isMatchingApply(tp1) || { - tycon2.info match { - case info2: TypeBounds => - compareLower(info2, tyconIsTypeRef = true) - case info2: ClassInfo => - val base = tp1.baseType(info2.cls) - if (base.exists && base.ne(tp1)) - if (tp1.isRef(info2.cls)) isSubType(base, tp2) - else isSubApproxLo(base, tp2) - else fourthTry(tp1, tp2) + /** Subtype test for the application `tp1 = tycon1[args1]`. + */ + def compareAppliedType1(tp1: AppliedType, tycon1: Type, args1: List[Type]): Boolean = + tycon1 match { + case param1: TypeParamRef => + def canInstantiate = tp2 match { + case AppliedType(tycon2, args2) => + tryInstantiate(param1, tycon2.ensureHK) && isSubArgs(args1, args2, tp1, tycon2.typeParams) case _ => - fourthTry(tp1, tp2) + false } - } - case _: TypeVar | _: AnnotatedType => - isSubType(tp1, tp2.superType) - case tycon2: AppliedType => - fallback(tycon2.lowerBound) - case _ => + canConstrain(param1) && canInstantiate || + isSubType(bounds(param1).hi.applyIfParameterized(args1), tp2, approx | LoApprox) + case tycon1: TypeRef if tycon1.symbol.isClass => + false + case tycon1: TypeProxy => + recur(tp1.superType, tp2) + case _ => + false + } + + /** Like tp1 <:< tp2, but returns false immediately if we know that + * the case was covered previously during subtyping. + */ + def isNewSubType(tp1: Type): Boolean = + if (isCovered(tp1) && isCovered(tp2)) { + //println(s"useless subtype: $tp1 <:< $tp2") false - } - } + } else isSubType(tp1, tp2, approx | LoApprox) - /** Subtype test for the application `tp1 = tycon1[args1]`. - */ - def compareAppliedType1(tp1: AppliedType, tycon1: Type, args1: List[Type], tp2: Type): Boolean = - tycon1 match { - case param1: TypeParamRef => - def canInstantiate = tp2 match { - case AppliedType(tycon2, args2) => - tryInstantiate(param1, tycon2.ensureHK) && isSubArgs(args1, args2, tp1, tycon2.typeParams) - case _ => - false + def recur(tp1: Type, tp2: Type) = isSubType(tp1, tp2, approx) + + // 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 < Config.LogPendingSubTypesThreshold) firstTry + else monitoredIsSubType + recCount = recCount - 1 + if (!result) state.resetConstraintTo(saved) + else if (recCount == 0 && needsGc) { + state.gc() + needsGc = false } - canConstrain(param1) && canInstantiate || - isSubApproxLo(bounds(param1).hi.applyIfParameterized(args1), tp2) - case tycon1: TypeRef if tycon1.symbol.isClass => - false - case tycon1: TypeProxy => - isSubType(tp1.superType, tp2) - case _ => - false + if (Stats.monitored) recordStatistics(result, savedSuccessCount) + result + } catch { + case NonFatal(ex) => + if (ex.isInstanceOf[AssertionError]) showGoal(tp1, tp2) + recCount -= 1 + state.resetConstraintTo(saved) + successCount = savedSuccessCount + throw ex + } } + } /** Subtype test for corresponding arguments in `args1`, `args2` according to - * variances in type parameters `tparams`. - */ + * variances in type parameters `tparams`. + */ def isSubArgs(args1: List[Type], args2: List[Type], tp1: Type, tparams: List[ParamInfo]): Boolean = if (args1.isEmpty) args2.isEmpty else args2.nonEmpty && { @@ -902,8 +876,8 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { case arg1: TypeBounds => compareCaptured(arg1, arg2) case _ => - (v > 0 || isSubTypePart(arg2, arg1)) && - (v < 0 || isSubTypePart(arg1, arg2)) + (v > 0 || isSubType(arg2, arg1)) && + (v < 0 || isSubType(arg1, arg2)) } } @@ -999,15 +973,6 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { } || op2 } - /** Like tp1 <:< tp2, but returns false immediately if we know that - * the case was covered previously during subtyping. - */ - private def isNewSubType(tp1: Type, tp2: Type): Boolean = - if (isCovered(tp1) && isCovered(tp2)) { - //println(s"useless subtype: $tp1 <:< $tp2") - false - } else isSubApproxLo(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, @@ -1035,7 +1000,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { tp2.refinedInfo match { case rinfo2: TypeBounds => val ref1 = tp1.widenExpr.select(name) - isSubTypePart(rinfo2.lo, ref1) && isSubType(ref1, rinfo2.hi) + isSubType(rinfo2.lo, ref1) && isSubType(ref1, rinfo2.hi) case _ => false } @@ -1043,7 +1008,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { } def qualifies(m: SingleDenotation) = - isSubTypePart(m.info, rinfo2) || matchAbstractTypeMember(m.info) + isSubType(m.info, rinfo2) || matchAbstractTypeMember(m.info) tp1.member(name) match { // inlined hasAltWith for performance case mbr: SingleDenotation => qualifies(mbr) @@ -1083,7 +1048,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { */ private def isSubRefinements(tp1: RefinedType, tp2: RefinedType, limit: Type): Boolean = { def hasSubRefinement(tp1: RefinedType, refine2: Type): Boolean = { - isSubTypePart(tp1.refinedInfo, refine2) || { + isSubType(tp1.refinedInfo, refine2) || { // last effort: try to adapt variances of higher-kinded types if this is sound. // TODO: Move this to eta-expansion? val adapted2 = refine2.adaptHkVariances(tp1.parent.member(tp1.refinedName).symbol.info) @@ -1133,8 +1098,8 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { * `bound` as an upper or lower bound (which depends on `isUpper`). * Test that the resulting bounds are still satisfiable. */ - private def narrowGADTBounds(tr: NamedType, bound: Type, isUpper: Boolean): Boolean = { - val boundIsPrecise = if (isUpper) hiIsPrecise else loIsPrecise + private def narrowGADTBounds(tr: NamedType, bound: Type, approx: ApproxState, isUpper: Boolean): Boolean = { + val boundIsPrecise = (approx & (if (isUpper) HiApprox else LoApprox)) == 0 ctx.mode.is(Mode.GADTflexible) && !frozenConstraint && boundIsPrecise && { val tparam = tr.symbol gadts.println(i"narrow gadt bound of $tparam: ${tparam.info} from ${if (isUpper) "above" else "below"} to $bound ${bound.toString} ${bound.isRef(tparam)}") @@ -1144,7 +1109,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { val newBounds = if (isUpper) TypeBounds(oldBounds.lo, oldBounds.hi & bound) else TypeBounds(oldBounds.lo | bound, oldBounds.hi) - isSubTypePart(newBounds.lo, newBounds.hi) && + isSubType(newBounds.lo, newBounds.hi) && { ctx.gadt.setBounds(tparam, newBounds); true } } } @@ -1661,6 +1626,11 @@ object TypeComparer { case _ => String.valueOf(res) } + type ApproxState = Int + val Precise = 0 + val LoApprox = 1 + val HiApprox = 2 + /** Show trace of comparison operations when performing `op` as result string */ def explained[T](op: Context => T)(implicit ctx: Context): String = { val nestedCtx = ctx.fresh.setTypeComparerFn(new ExplainingTypeComparer(_)) @@ -1671,7 +1641,7 @@ object TypeComparer { /** A type comparer that can record traces of subtype operations */ class ExplainingTypeComparer(initctx: Context) extends TypeComparer(initctx) { - import TypeComparer.show + import TypeComparer._ private[this] var indent = 0 private val b = new StringBuilder @@ -1689,9 +1659,9 @@ class ExplainingTypeComparer(initctx: Context) extends TypeComparer(initctx) { res } - override def isSubType(tp1: Type, tp2: Type) = - traceIndented(s"${show(tp1)} <:< ${show(tp2)}${if (Config.verboseExplainSubtype) s" ${tp1.getClass} ${tp2.getClass}" else ""}${if (frozenConstraint) " frozen" else ""}") { - super.isSubType(tp1, tp2) + override def isSubType(tp1: Type, tp2: Type, approx: ApproxState) = + traceIndented(s"${show(tp1)} <:< ${show(tp2)}${if (Config.verboseExplainSubtype) s" ${tp1.getClass} ${tp2.getClass}" else ""} $approx ${if (frozenConstraint) " frozen" else ""}") { + super.isSubType(tp1, tp2, approx) } override def hasMatchingMember(name: Name, tp1: Type, tp2: RefinedType): Boolean = @@ -1716,19 +1686,5 @@ class ExplainingTypeComparer(initctx: Context) extends TypeComparer(initctx) { override def copyIn(ctx: Context) = new ExplainingTypeComparer(ctx) - override def compareAppliedType2(tp1: Type, tp2: AppliedType, tycon2: Type, args2: List[Type]): Boolean = { - def addendum = "" - traceIndented(i"compareAppliedType2 $tp1, $tp2$addendum") { - super.compareAppliedType2(tp1, tp2, tycon2, args2) - } - } - - override def compareAppliedType1(tp1: AppliedType, tycon1: Type, args1: List[Type], tp2: Type): Boolean = { - def addendum = "" - traceIndented(i"compareAppliedType1 $tp1, $tp2$addendum") { - super.compareAppliedType1(tp1, tycon1, args1, tp2) - } - } - override def toString = "Subtype trace:" + { try b.toString finally b.clear() } } diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 8568e33def48..e7f3acf4d033 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -127,9 +127,7 @@ object Types { def isRef(sym: Symbol)(implicit ctx: Context): Boolean = stripAnnots.stripTypeVar match { case this1: TypeRef => this1.info match { // see comment in Namer#typeDefSig - case TypeAlias(tp) => - assert((tp ne this) && (tp ne this1), s"$tp / $this") - tp.isRef(sym) + case TypeAlias(tp) => tp.isRef(sym) case _ => this1.symbol eq sym } case this1: RefinedOrRecType => From 8a1c76e6e62c9ef0e000df14a9b4d4104a356560 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 25 Jan 2018 22:42:31 +0100 Subject: [PATCH 07/13] Make ApproxState a value class --- .../dotty/tools/dotc/core/TypeComparer.scala | 48 ++++++++++++------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index eb445970e703..3f661536e02b 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -107,7 +107,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { assert(isSatisfiable, constraint.show) } - protected def isSubType(tp1: Type, tp2: Type): Boolean = isSubType(tp1, tp2, Precise) + protected def isSubType(tp1: Type, tp2: Type): Boolean = isSubType(tp1, tp2, NoApprox) protected def isSubType(tp1: Type, tp2: Type, approx: ApproxState): Boolean = trace(s"isSubType ${traceInfo(tp1, tp2)} $approx", subtyping) { @@ -289,7 +289,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { def compareTypeParamRef = ctx.mode.is(Mode.TypevarsMissContext) || isSubTypeWhenFrozen(bounds(tp1).hi, tp2) || { - if (canConstrain(tp1) && (approx & HiApprox) == 0) + if (canConstrain(tp1) && !approx.high) addConstraint(tp1, tp2, fromBelow = false) && flagNothingBound else thirdTry } @@ -355,8 +355,8 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { GADTusage(tp2.symbol) } val tryLowerFirst = frozenConstraint || !isCappable(tp1) - if (tryLowerFirst) isSubType(tp1, lo2, approx | HiApprox) || compareGADT || fourthTry - else compareGADT || fourthTry || isSubType(tp1, lo2, approx | HiApprox) + if (tryLowerFirst) isSubType(tp1, lo2, approx.addHigh) || compareGADT || fourthTry + else compareGADT || fourthTry || isSubType(tp1, lo2, approx.addHigh) case _ => val cls2 = tp2.symbol @@ -370,7 +370,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { // If `cls2` is parameterized, we are seeing a raw type, so we need to compare only the symbol return base.typeSymbol == cls2 if (base ne tp1) - return isSubType(base, tp2, if (tp1.isRef(cls2)) approx else approx | LoApprox) + return isSubType(base, tp2, if (tp1.isRef(cls2)) approx else approx.addLow) } if (cls2 == defn.SingletonClass && tp1.isStable) return true } @@ -397,7 +397,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { if (frozenConstraint) isSubType(tp1, bounds(tp2).lo) else isSubTypeWhenFrozen(tp1, tp2) alwaysTrue || { - if (canConstrain(tp2) && (approx & LoApprox) == 0) + if (canConstrain(tp2) && !approx.low) addConstraint(tp2, tp1.widenExpr, fromBelow = true) else fourthTry } @@ -564,7 +564,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { narrowGADTBounds(tp1, tp2, approx, isUpper = true)) && GADTusage(tp1.symbol) } - isSubType(hi1, tp2, approx | LoApprox) || compareGADT + isSubType(hi1, tp2, approx.addLow) || compareGADT case _ => def isNullable(tp: Type): Boolean = tp.widenDealias match { case tp: TypeRef => tp.symbol.isNullableClass @@ -743,7 +743,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { * @param tyconLo The type constructor's lower approximation. */ def fallback(tyconLo: Type) = - either(fourthTry, isSubType(tp1, tyconLo.applyIfParameterized(args2), approx | HiApprox)) + either(fourthTry, isSubType(tp1, tyconLo.applyIfParameterized(args2), approx.addHigh)) /** Let `tycon2bounds` be the bounds of the RHS type constructor `tycon2`. * Let `app2 = tp2` where the type constructor of `tp2` is replaced by @@ -757,7 +757,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { def compareLower(tycon2bounds: TypeBounds, tyconIsTypeRef: Boolean): Boolean = if (tycon2bounds.lo eq tycon2bounds.hi) if (tyconIsTypeRef) recur(tp1, tp2.superType) - else isSubType(tp1, tycon2bounds.lo.applyIfParameterized(args2), approx | HiApprox) + else isSubType(tp1, tycon2bounds.lo.applyIfParameterized(args2), approx.addHigh) else fallback(tycon2bounds.lo) @@ -774,7 +774,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { case info2: ClassInfo => val base = tp1.baseType(info2.cls) if (base.exists && base.ne(tp1)) - isSubType(base, tp2, if (tp1.isRef(info2.cls)) approx else approx | LoApprox) + isSubType(base, tp2, if (tp1.isRef(info2.cls)) approx else approx.addLow) else fourthTry case _ => fourthTry @@ -801,7 +801,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { false } canConstrain(param1) && canInstantiate || - isSubType(bounds(param1).hi.applyIfParameterized(args1), tp2, approx | LoApprox) + isSubType(bounds(param1).hi.applyIfParameterized(args1), tp2, approx.addLow) case tycon1: TypeRef if tycon1.symbol.isClass => false case tycon1: TypeProxy => @@ -817,7 +817,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { if (isCovered(tp1) && isCovered(tp2)) { //println(s"useless subtype: $tp1 <:< $tp2") false - } else isSubType(tp1, tp2, approx | LoApprox) + } else isSubType(tp1, tp2, approx.addLow) def recur(tp1: Type, tp2: Type) = isSubType(tp1, tp2, approx) @@ -1099,8 +1099,8 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { * Test that the resulting bounds are still satisfiable. */ private def narrowGADTBounds(tr: NamedType, bound: Type, approx: ApproxState, isUpper: Boolean): Boolean = { - val boundIsPrecise = (approx & (if (isUpper) HiApprox else LoApprox)) == 0 - ctx.mode.is(Mode.GADTflexible) && !frozenConstraint && boundIsPrecise && { + val boundImprecise = if (isUpper) approx.high else approx.low + ctx.mode.is(Mode.GADTflexible) && !frozenConstraint && !boundImprecise && { val tparam = tr.symbol gadts.println(i"narrow gadt bound of $tparam: ${tparam.info} from ${if (isUpper) "above" else "below"} to $bound ${bound.toString} ${bound.isRef(tparam)}") if (bound.isRef(tparam)) false @@ -1626,10 +1626,22 @@ object TypeComparer { case _ => String.valueOf(res) } - type ApproxState = Int - val Precise = 0 - val LoApprox = 1 - val HiApprox = 2 + private val LoApprox = 1 + private val HiApprox = 2 + + class ApproxState(private val bits: Int) extends AnyVal { + override def toString = { + val lo = if ((bits & LoApprox) != 0) "LoApprox" else "" + val hi = if ((bits & HiApprox) != 0) "HiApprox" else "" + lo ++ hi + } + def addLow = new ApproxState(bits | LoApprox) + def addHigh = new ApproxState(bits | HiApprox) + def low = (bits & LoApprox) != 0 + def high = (bits & HiApprox) != 0 + } + + val NoApprox = new ApproxState(0) /** Show trace of comparison operations when performing `op` as result string */ def explained[T](op: Context => T)(implicit ctx: Context): String = { From 4697e49c8468cbca8fa263fb1794a59dc18d16b3 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 25 Jan 2018 23:00:17 +0100 Subject: [PATCH 08/13] Get rid of isCappable With the new info about approximations it's no longer needed. --- .../dotty/tools/dotc/core/TypeComparer.scala | 20 ++----------------- 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 3f661536e02b..8e842b33b111 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -354,9 +354,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { narrowGADTBounds(tp2, tp1, approx, isUpper = false)) && GADTusage(tp2.symbol) } - val tryLowerFirst = frozenConstraint || !isCappable(tp1) - if (tryLowerFirst) isSubType(tp1, lo2, approx.addHigh) || compareGADT || fourthTry - else compareGADT || fourthTry || isSubType(tp1, lo2, approx.addHigh) + isSubType(tp1, lo2, approx.addHigh) || compareGADT || fourthTry case _ => val cls2 = tp2.symbol @@ -389,7 +387,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { val alwaysTrue = // The following condition is carefully formulated to catch all cases // where the subtype relation is true without needing to add a constraint - // It's tricky because we might need to either appriximate tp2 by its + // It's tricky because we might need to either approximate tp2 by its // lower bound or else widen tp1 and check that the result is a subtype of tp2. // So if the constraint is not yet frozen, we do the same comparison again // with a frozen constraint, which means that we get a chance to do the @@ -1080,20 +1078,6 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { case _ => proto.isMatchedBy(tp) } - /** Can type `tp` be constrained from above, either by adding a constraint to - * a typevar that it refers to, or by narrowing a GADT bound? In that case we have - * to be careful not to approximate with the lower bound of a type in `thirdTry`. - * Instead, we should first unroll `tp1` until we hit the type variable and bind the - * type variable with (the corresponding type in) `tp2` instead. Or, in the - * case of a GADT bounded typeref, we should narrow with `tp2` instead of its lower bound. - */ - private def isCappable(tp: Type): Boolean = tp match { - case tp: TypeParamRef => constraint contains tp - case tp: TypeProxy => isCappable(tp.underlying) - case tp: AndOrType => isCappable(tp.tp1) || isCappable(tp.tp2) - case _ => false - } - /** Narrow gadt.bounds for the type parameter referenced by `tr` to include * `bound` as an upper or lower bound (which depends on `isUpper`). * Test that the resulting bounds are still satisfiable. From 2fb548adafe9d5f5eb51f07d32bc37a55c1c3e31 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 26 Jan 2018 09:53:02 +0100 Subject: [PATCH 09/13] Revert to keeping approx as TypeComparer state Passing it as an additional parameter seemed to cause a slight (~2%) performance degradation. --- .../src/dotty/tools/dotc/core/TypeComparer.scala | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 8e842b33b111..0e43f705caac 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -107,9 +107,18 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { assert(isSatisfiable, constraint.show) } + private[this] var approx: ApproxState = NoApprox + protected def approxState = approx + + protected def isSubType(tp1: Type, tp2: Type, a: ApproxState): Boolean = { + val saved = approx + this.approx = a + try recur(tp1, tp2) finally this.approx = saved + } + protected def isSubType(tp1: Type, tp2: Type): Boolean = isSubType(tp1, tp2, NoApprox) - protected def isSubType(tp1: Type, tp2: Type, approx: ApproxState): Boolean = trace(s"isSubType ${traceInfo(tp1, tp2)} $approx", subtyping) { + protected def recur(tp1: Type, tp2: Type): Boolean = trace(s"isSubType ${traceInfo(tp1, tp2)} $approx", subtyping) { def monitoredIsSubType = { if (pendingSubTypes == null) { @@ -817,9 +826,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { false } else isSubType(tp1, tp2, approx.addLow) - def recur(tp1: Type, tp2: Type) = isSubType(tp1, tp2, approx) - - // begin isSubType + // begin recur if (tp2 eq NoType) false else if (tp1 eq tp2) true else { From 9ea0c8bbe1c3a45af5b7925d23e49adee87fad5c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 26 Jan 2018 11:06:32 +0100 Subject: [PATCH 10/13] Avoid isSubType recursions involving Nothing as upper bound --- compiler/src/dotty/tools/dotc/core/TypeComparer.scala | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 0e43f705caac..02c477e708ef 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -363,7 +363,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { narrowGADTBounds(tp2, tp1, approx, isUpper = false)) && GADTusage(tp2.symbol) } - isSubType(tp1, lo2, approx.addHigh) || compareGADT || fourthTry + isSubApproxHi(tp1, lo2) || compareGADT || fourthTry case _ => val cls2 = tp2.symbol @@ -750,7 +750,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { * @param tyconLo The type constructor's lower approximation. */ def fallback(tyconLo: Type) = - either(fourthTry, isSubType(tp1, tyconLo.applyIfParameterized(args2), approx.addHigh)) + either(fourthTry, isSubApproxHi(tp1, tyconLo.applyIfParameterized(args2))) /** Let `tycon2bounds` be the bounds of the RHS type constructor `tycon2`. * Let `app2 = tp2` where the type constructor of `tp2` is replaced by @@ -764,7 +764,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { def compareLower(tycon2bounds: TypeBounds, tyconIsTypeRef: Boolean): Boolean = if (tycon2bounds.lo eq tycon2bounds.hi) if (tyconIsTypeRef) recur(tp1, tp2.superType) - else isSubType(tp1, tycon2bounds.lo.applyIfParameterized(args2), approx.addHigh) + else isSubApproxHi(tp1, tycon2bounds.lo.applyIfParameterized(args2)) else fallback(tycon2bounds.lo) @@ -826,6 +826,9 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { false } else isSubType(tp1, tp2, approx.addLow) + def isSubApproxHi(tp1: Type, tp2: Type): Boolean = + (tp2 ne NothingType) && isSubType(tp1, tp2, approx.addHigh) + // begin recur if (tp2 eq NoType) false else if (tp1 eq tp2) true From bee1ab65400cacf82a2c8cb78f7be4157f9f9caa Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Fri, 2 Feb 2018 00:17:39 +0100 Subject: [PATCH 11/13] Fix DottyBytecodeTests#efficientTryCases toTypeTree now wraps TypeTrees types with an annotation in some cases. --- compiler/src/dotty/tools/dotc/transform/TryCatchPatterns.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/TryCatchPatterns.scala b/compiler/src/dotty/tools/dotc/transform/TryCatchPatterns.scala index eb4183167097..59be424e746c 100644 --- a/compiler/src/dotty/tools/dotc/transform/TryCatchPatterns.scala +++ b/compiler/src/dotty/tools/dotc/transform/TryCatchPatterns.scala @@ -71,7 +71,7 @@ class TryCatchPatterns extends MiniPhase { case _ => isDefaultCase(cdef) } - private def isSimpleThrowable(tp: Type)(implicit ctx: Context): Boolean = tp match { + private def isSimpleThrowable(tp: Type)(implicit ctx: Context): Boolean = tp.stripAnnots match { case tp @ TypeRef(pre, _) => (pre == NoPrefix || pre.widen.typeSymbol.isStatic) && // Does not require outer class check !tp.symbol.is(Flags.Trait) && // Traits not supported by JVM From b42da43da19da4bb033d8d84ea43c6ed38ee1826 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 2 Feb 2018 22:20:46 +0100 Subject: [PATCH 12/13] Fix doc comment --- .../tools/dotc/transform/FirstTransform.scala | 25 +++---------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala index 4905861f4bed..cceda89e2976 100644 --- a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala +++ b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala @@ -61,23 +61,6 @@ class FirstTransform extends MiniPhase with InfoTransformer { thisPhase => tp } - /* - tp match { - //create companions for value classes that are not from currently compiled source file - case tp@ClassInfo(_, cls, _, decls, _) - if (ValueClasses.isDerivedValueClass(cls)) && - !sym.isDefinedInCurrentRun && sym.scalacLinkedClass == NoSymbol => - val newDecls = decls.cloneScope - val (modul, mcMethod, symMethod) = newCompanion(sym.name.toTermName, sym) - modul.entered - mcMethod.entered - newDecls.enter(symMethod) - tp.derivedClassInfo(decls = newDecls) - case _ => tp - } - } - */ - override def checkPostCondition(tree: Tree)(implicit ctx: Context): Unit = { tree match { case Select(qual, name) if !name.is(OuterSelectName) && tree.symbol.exists => @@ -199,11 +182,9 @@ class FirstTransform extends MiniPhase with InfoTransformer { thisPhase => } } - /** Replace type tree `t` of type `T` with `TypeTree(T)`, but make sure all - * binders in `t` are maintained by rewrapping binders around the type tree. - * E.g. if `t` is `C[t @ (>: L <: H)]`, replace with - * `t @ TC[_ >: L <: H]`. The body of the binder `t` is now wrong, but this does - * not matter, as we only need the info of `t`. + /** Replace type tree `t` of type `T` with `TypeTree(T)`, but record all + * nested Bind nodes in annotations. These are interpreted in TreeTypeMaps + * so that bound symbols can be properly copied. */ private def toTypeTree(tree: Tree)(implicit ctx: Context) = { val binders = collectBinders.apply(Nil, tree) From e07af3fe773b8c0610d80e6c4edf8c797c1fe7df Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 3 Feb 2018 11:46:52 +0100 Subject: [PATCH 13/13] Fix rebase breakage The fix of #3627 got lost when merging the GADT subtype changes. --- compiler/src/dotty/tools/dotc/core/TypeComparer.scala | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 02c477e708ef..8ed5758a91ac 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -25,6 +25,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { private[this] var pendingSubTypes: mutable.Set[(Type, Type)] = null private[this] var recCount = 0 + private[this] var monitored = false private[this] var needsGc = false @@ -102,9 +103,11 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { if (tp2 eq NoType) return false if ((tp2 eq tp1) || (tp2 eq WildcardType)) return true try isSubType(tp1, tp2) - finally + finally { + monitored = false if (Config.checkConstraintsSatisfiable) assert(isSatisfiable, constraint.show) + } } private[this] var approx: ApproxState = NoApprox @@ -284,6 +287,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { if (recur(info1.alias, tp2)) return true if (tp1.prefix.isStable) return false case _ => + if (tp1 eq NothingType) return tp1 == tp2.bottomType } thirdTry case tp1: TypeParamRef => @@ -837,9 +841,8 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { val savedSuccessCount = successCount try { recCount = recCount + 1 - val result = - if (recCount < Config.LogPendingSubTypesThreshold) firstTry - else monitoredIsSubType + if (recCount >= Config.LogPendingSubTypesThreshold) monitored = true + val result = if (monitored) monitoredIsSubType else firstTry recCount = recCount - 1 if (!result) state.resetConstraintTo(saved) else if (recCount == 0 && needsGc) {