From 416fadce3950e41480313ed6b06391d179e2a27b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 31 Jul 2017 17:53:15 +0200 Subject: [PATCH 01/13] Generalize approximating type maps. Approximating type maps now work with bounds instead of NoTypes. This gives more precision (in particular for type applications), and also makes it possible to combine approximation with other mappings. As a side-effect we provide the hooks for preventing constructing illegal types C#A where C is non-singleton and A is abstract by setting variance to 0 for the prefix of an abstract type selection. There's one test case that fails: One of the types in dependent-exractors.scala does not check out anymore. This has likely to do with the loss of precision incurred by the maps. Exact cause remains to be tracked down. --- .../tools/dotc/core/ConstraintHandling.scala | 6 +- .../src/dotty/tools/dotc/core/TypeOps.scala | 13 +- .../src/dotty/tools/dotc/core/Types.scala | 188 ++++++++++++++---- tests/pos/dependent-extractors.scala | 2 +- 4 files changed, 154 insertions(+), 55 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala index 15f24a7fe50c..81ee865aa96c 100644 --- a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -362,13 +362,11 @@ trait ConstraintHandling { def pruneLambdaParams(tp: Type) = if (comparedTypeLambdas.nonEmpty) { val approx = new ApproximatingTypeMap { + if (fromBelow) variance = -1 def apply(t: Type): Type = t match { case t @ TypeParamRef(tl: TypeLambda, n) if comparedTypeLambdas contains tl => - val effectiveVariance = if (fromBelow) -variance else variance val bounds = tl.paramInfos(n) - if (effectiveVariance > 0) bounds.lo - else if (effectiveVariance < 0) bounds.hi - else NoType + range(bounds.lo, bounds.hi) case _ => mapOver(t) } diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index 829b5acec00c..e1ba636d820e 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -132,18 +132,9 @@ trait TypeOps { this: Context => // TODO: Make standalone object. /** Approximate a type `tp` with a type that does not contain skolem types. */ object deskolemize extends ApproximatingTypeMap { - private var seen: Set[SkolemType] = Set() def apply(tp: Type) = tp match { - case tp: SkolemType => - if (seen contains tp) NoType - else { - val saved = seen - seen += tp - try approx(hi = tp.info) - finally seen = saved - } - case _ => - mapOver(tp) + case tp: SkolemType => range(hi = apply(tp.info)) + case _ => mapOver(tp) } } diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 3dd56aa1cebf..474566a0f739 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -136,6 +136,12 @@ object Types { case _ => false } + /** Is this type exactly Nothing (no vars, aliases, refinements etc allowed)? */ + def isBottomType(implicit ctx: Context): Boolean = this match { + case tp: TypeRef => tp.symbol eq defn.NothingClass + case _ => false + } + /** Is this type a (neither aliased nor applied) reference to class `sym`? */ def isDirectRef(sym: Symbol)(implicit ctx: Context): Boolean = stripTypeVar match { case this1: TypeRef => @@ -275,7 +281,10 @@ object Types { } /** Is this an alias TypeBounds? */ - def isAlias: Boolean = this.isInstanceOf[TypeAlias] + final def isAlias: Boolean = this.isInstanceOf[TypeAlias] + + /** Is this a non-alias TypeBounds? */ + final def isRealTypeBounds = this.isInstanceOf[TypeBounds] && !isAlias // ----- Higher-order combinators ----------------------------------- @@ -1220,6 +1229,18 @@ object Types { case _ => TypeAlias(this) } + /** The lower bound of a TypeBounds type, the type itself otherwise */ + def loBound = this match { + case tp: TypeBounds => tp.lo + case _ => this + } + + /** The upper bound of a TypeBounds type, the type itself otherwise */ + def hiBound = this match { + case tp: TypeBounds => tp.hi + case _ => this + } + /** The type parameter with given `name`. This tries first `decls` * in order not to provoke a cycle by forcing the info. If that yields * no symbol it tries `member` as an alternative. @@ -1766,6 +1787,7 @@ object Types { */ def derivedSelect(prefix: Type)(implicit ctx: Context): Type = if (prefix eq this.prefix) this + else if (prefix.isBottomType) prefix else if (isType) { val res = prefix.lookupRefined(name) if (res.exists) res @@ -3722,6 +3744,18 @@ object Types { // of `p`'s upper bound. val prefix1 = this(tp.prefix) variance = saved + /* was: + val prefix1 = tp.info match { + case info: TypeBounds if !info.isAlias => + // prefix of an abstract type selection is non-variant, since a path + // cannot be legally widened to its underlying type, or any supertype. + val saved = variance + variance = 0 + try this(tp.prefix) finally variance = saved + case _ => + this(tp.prefix) + } + */ derivedSelect(tp, prefix1) } case _: ThisType @@ -3851,63 +3885,139 @@ object Types { def apply(tp: Type) = tp } - /** A type map that approximates NoTypes by upper or lower known bounds depending on + case class Range(lo: Type, hi: Type) extends UncachedGroundType { + assert(!lo.isInstanceOf[Range]) + assert(!hi.isInstanceOf[Range]) + } + + /** A type map that approximates TypeBounds types depending on * variance. * * if variance > 0 : approximate by upper bound * variance < 0 : approximate by lower bound - * variance = 0 : propagate NoType to next outer level + * variance = 0 : propagate bounds to next outer level */ abstract class ApproximatingTypeMap(implicit ctx: Context) extends TypeMap { thisMap => - def approx(lo: Type = defn.NothingType, hi: Type = defn.AnyType) = - if (variance == 0) NoType - else apply(if (variance < 0) lo else hi) + + def range(lo: Type = defn.NothingType, hi: Type = defn.AnyType) = + if (variance > 0) hi + else if (variance < 0) lo + else Range(loBound(lo), hiBound(hi)) + + def isRange(tp: Type) = tp.isInstanceOf[Range] + + def loBound(tp: Type) = tp match { + case tp: Range => tp.lo + case _ => tp + } + + /** The upper bound of a TypeBounds type, the type itself otherwise */ + def hiBound(tp: Type) = tp match { + case tp: Range => tp.hi + case _ => tp + } + + def rangeToBounds(tp: Type) = tp match { + case Range(lo, hi) => TypeBounds(lo, hi) + case _ => tp + } override protected def derivedSelect(tp: NamedType, pre: Type) = if (pre eq tp.prefix) tp - else tp.info match { - case TypeAlias(alias) => apply(alias) // try to heal by following aliases - case _ => - if (pre.exists && !pre.isRef(defn.NothingClass) && variance > 0) tp.derivedSelect(pre) - else tp.info match { - case TypeBounds(lo, hi) => approx(lo, hi) - case _ => approx() + else pre match { + case Range(preLo, preHi) => + tp.info match { + case TypeAlias(alias) => apply(alias) + case TypeBounds(lo, hi) => range(apply(lo), apply(hi)) + case _ => range(tp.derivedSelect(preLo), tp.derivedSelect(preHi)) } + case _ => tp.derivedSelect(pre) } + override protected def derivedRefinedType(tp: RefinedType, parent: Type, info: Type) = - if (parent.exists && info.exists) tp.derivedRefinedType(parent, tp.refinedName, info) - else approx(hi = parent) + parent match { + case Range(parentLo, parentHi) => + range(derivedRefinedType(tp, parentLo, info), derivedRefinedType(tp, parentHi, info)) + case _ => + if (parent.isBottomType) parent + else info match { + case Range(infoLo, infoHi) if tp.refinedName.isTermName || variance <= 0 => + range(derivedRefinedType(tp, parent, infoLo), derivedRefinedType(tp, parent, infoHi)) + case _ => + tp.derivedRefinedType(parent, tp.refinedName, rangeToBounds(info)) + } + } override protected def derivedRecType(tp: RecType, parent: Type) = - if (parent.exists) tp.rebind(parent) - else approx() + parent match { + case Range(lo, hi) => range(tp.rebind(lo), tp.rebind(hi)) + case _ => tp.rebind(parent) + } override protected def derivedTypeAlias(tp: TypeAlias, alias: Type) = - if (alias.exists) tp.derivedTypeAlias(alias) - else approx(NoType, TypeBounds.empty) + alias match { + case Range(lo, hi) => + if (variance > 0) TypeBounds(lo, hi) + else range(TypeAlias(lo), TypeAlias(hi)) + case _ => tp.derivedTypeAlias(alias) + } override protected def derivedTypeBounds(tp: TypeBounds, lo: Type, hi: Type) = - if (lo.exists && hi.exists) tp.derivedTypeBounds(lo, hi) - else approx(NoType, - if (lo.exists) TypeBounds.lower(lo) - else if (hi.exists) TypeBounds.upper(hi) - else TypeBounds.empty) + if (isRange(lo) || isRange(hi)) + if (variance > 0) TypeBounds(loBound(lo), hiBound(hi)) + else range(TypeBounds(hiBound(lo), loBound(hi)), TypeBounds(loBound(lo), hiBound(hi))) + else tp.derivedTypeBounds(lo, hi) override protected def derivedSuperType(tp: SuperType, thistp: Type, supertp: Type) = - if (thistp.exists && supertp.exists) tp.derivedSuperType(thistp, supertp) - else NoType + if (isRange(thistp) || isRange(supertp)) range() + else tp.derivedSuperType(thistp, supertp) + override protected def derivedAppliedType(tp: HKApply, tycon: Type, args: List[Type]): Type = - if (tycon.exists && args.forall(_.exists)) tp.derivedAppliedType(tycon, args) - else approx() // This is rather coarse, but to do better is a bit complicated + tycon match { + case Range(tyconLo, tyconHi) => + range(derivedAppliedType(tp, tyconLo, args), derivedAppliedType(tp, tyconHi, args)) + case _ => + if (args.exists(isRange)) + if (variance > 0) tp.derivedAppliedType(tycon, args.map(rangeToBounds)) + else { + val loBuf, hiBuf = new mutable.ListBuffer[Type] + def distributeArgs(args: List[Type], tparams: List[ParamInfo]): Boolean = args match { + case Range(lo, hi) :: args1 => + val v = tparams.head.paramVariance + if (v == 0) false + else if (v > 0) { loBuf += lo; hiBuf += hi } + else { loBuf += hi; hiBuf += lo } + distributeArgs(args1, tparams.tail) + case arg :: args1 => + loBuf += arg; hiBuf += arg + distributeArgs(args1, tparams.tail) + case nil => + true + } + if (distributeArgs(args, tp.typeParams)) + range(tp.derivedAppliedType(tycon, loBuf.toList), + tp.derivedAppliedType(tycon, hiBuf.toList)) + else range() + } + else tp.derivedAppliedType(tycon, args) + } + override protected def derivedAndOrType(tp: AndOrType, tp1: Type, tp2: Type) = - if (tp1.exists && tp2.exists) tp.derivedAndOrType(tp1, tp2) - else if (tp.isAnd) approx(hi = tp1 & tp2) // if one of tp1d, tp2d exists, it is the result of tp1d & tp2d - else approx(lo = tp1 & tp2) + if (tp1.isInstanceOf[Range] || tp2.isInstanceOf[Range]) + if (tp.isAnd) range(loBound(tp1) & loBound(tp2), hiBound(tp1) & hiBound(tp2)) + else range(loBound(tp1) | loBound(tp2), hiBound(tp1) | hiBound(tp2)) + else tp.derivedAndOrType(tp1, tp2) override protected def derivedAnnotatedType(tp: AnnotatedType, underlying: Type, annot: Annotation) = - if (underlying.exists) tp.derivedAnnotatedType(underlying, annot) - else NoType - override protected def derivedWildcardType(tp: WildcardType, bounds: Type) = - if (bounds.exists) tp.derivedWildcardType(bounds) - else WildcardType - override protected def derivedClassInfo(tp: ClassInfo, pre: Type): Type = - if (pre.exists) tp.derivedClassInfo(pre) - else NoType + underlying match { + case Range(lo, hi) => + range(tp.derivedAnnotatedType(lo, annot), tp.derivedAnnotatedType(hi, annot)) + case _ => + if (underlying.isBottomType) underlying + else tp.derivedAnnotatedType(underlying, annot) + } + override protected def derivedWildcardType(tp: WildcardType, bounds: Type) = { + tp.derivedWildcardType(rangeToBounds(bounds)) + } + override protected def derivedClassInfo(tp: ClassInfo, pre: Type): Type = { + assert(!pre.isInstanceOf[Range]) + tp.derivedClassInfo(pre) + } } // ----- TypeAccumulators ---------------------------------------------------- diff --git a/tests/pos/dependent-extractors.scala b/tests/pos/dependent-extractors.scala index 4d0830155fd2..39d7da039bb7 100644 --- a/tests/pos/dependent-extractors.scala +++ b/tests/pos/dependent-extractors.scala @@ -10,5 +10,5 @@ object Test { val y1: Int = y val z = (c: Any) match { case X(y) => y } - val z1: C#T = z + // val z1: C#T = z // error: z has type Any TODO: find out why } From 45bfa081568df7eecb01271c8bc476c383b69fc5 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 24 Jul 2017 18:02:36 +0200 Subject: [PATCH 02/13] Make applications inside ranges respect variance A Range, if it survives, will always lead to a situation where the upper bound appears in a covariant position in the result, and the lower bound appears in a contravariant position. Hence, when we apply a type map to the argument in a range we should take this into account. Fixes a previous failure in t2435.scala and dependent-extractors.scala. --- .../src/dotty/tools/dotc/core/TypeOps.scala | 8 ++++--- .../src/dotty/tools/dotc/core/Types.scala | 24 ++++++++++++++++--- tests/pos/dependent-extractors.scala | 2 +- tests/pos/t2435.scala | 3 ++- 4 files changed, 29 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index e1ba636d820e..54274be4ac95 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -132,9 +132,11 @@ trait TypeOps { this: Context => // TODO: Make standalone object. /** Approximate a type `tp` with a type that does not contain skolem types. */ object deskolemize extends ApproximatingTypeMap { - def apply(tp: Type) = tp match { - case tp: SkolemType => range(hi = apply(tp.info)) - case _ => mapOver(tp) + def apply(tp: Type) = /*ctx.traceIndented(i"deskolemize($tp) at $variance", show = true)*/ { + tp match { + case tp: SkolemType => range(hi = atVariance(1)(apply(tp.info))) + case _ => mapOver(tp) + } } } diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 474566a0f739..9150714dbd52 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -3888,6 +3888,9 @@ object Types { case class Range(lo: Type, hi: Type) extends UncachedGroundType { assert(!lo.isInstanceOf[Range]) assert(!hi.isInstanceOf[Range]) + + override def toText(printer: Printer): Text = + lo.toText(printer) ~ ".." ~ hi.toText(printer) } /** A type map that approximates TypeBounds types depending on @@ -3922,14 +3925,23 @@ object Types { case _ => tp } + def atVariance[T](v: Int)(op: => T): T = { + val saved = variance + variance = v + try op finally variance = saved + } + override protected def derivedSelect(tp: NamedType, pre: Type) = if (pre eq tp.prefix) tp else pre match { case Range(preLo, preHi) => tp.info match { - case TypeAlias(alias) => apply(alias) - case TypeBounds(lo, hi) => range(apply(lo), apply(hi)) - case _ => range(tp.derivedSelect(preLo), tp.derivedSelect(preHi)) + case TypeAlias(alias) => + apply(alias) + case TypeBounds(lo, hi) => + range(atVariance(-1)(apply(lo)), atVariance(1)(apply(hi))) + case _ => + range(tp.derivedSelect(preLo), tp.derivedSelect(preHi)) } case _ => tp.derivedSelect(pre) } @@ -3947,11 +3959,13 @@ object Types { tp.derivedRefinedType(parent, tp.refinedName, rangeToBounds(info)) } } + override protected def derivedRecType(tp: RecType, parent: Type) = parent match { case Range(lo, hi) => range(tp.rebind(lo), tp.rebind(hi)) case _ => tp.rebind(parent) } + override protected def derivedTypeAlias(tp: TypeAlias, alias: Type) = alias match { case Range(lo, hi) => @@ -3959,11 +3973,13 @@ object Types { else range(TypeAlias(lo), TypeAlias(hi)) case _ => tp.derivedTypeAlias(alias) } + override protected def derivedTypeBounds(tp: TypeBounds, lo: Type, hi: Type) = if (isRange(lo) || isRange(hi)) if (variance > 0) TypeBounds(loBound(lo), hiBound(hi)) else range(TypeBounds(hiBound(lo), loBound(hi)), TypeBounds(loBound(lo), hiBound(hi))) else tp.derivedTypeBounds(lo, hi) + override protected def derivedSuperType(tp: SuperType, thistp: Type, supertp: Type) = if (isRange(thistp) || isRange(supertp)) range() else tp.derivedSuperType(thistp, supertp) @@ -4003,6 +4019,7 @@ object Types { if (tp.isAnd) range(loBound(tp1) & loBound(tp2), hiBound(tp1) & hiBound(tp2)) else range(loBound(tp1) | loBound(tp2), hiBound(tp1) | hiBound(tp2)) else tp.derivedAndOrType(tp1, tp2) + override protected def derivedAnnotatedType(tp: AnnotatedType, underlying: Type, annot: Annotation) = underlying match { case Range(lo, hi) => @@ -4014,6 +4031,7 @@ object Types { override protected def derivedWildcardType(tp: WildcardType, bounds: Type) = { tp.derivedWildcardType(rangeToBounds(bounds)) } + override protected def derivedClassInfo(tp: ClassInfo, pre: Type): Type = { assert(!pre.isInstanceOf[Range]) tp.derivedClassInfo(pre) diff --git a/tests/pos/dependent-extractors.scala b/tests/pos/dependent-extractors.scala index 39d7da039bb7..c287a59c890a 100644 --- a/tests/pos/dependent-extractors.scala +++ b/tests/pos/dependent-extractors.scala @@ -10,5 +10,5 @@ object Test { val y1: Int = y val z = (c: Any) match { case X(y) => y } - // val z1: C#T = z // error: z has type Any TODO: find out why + val z1: C#T = z // error: z has type Any TODO: find out why } diff --git a/tests/pos/t2435.scala b/tests/pos/t2435.scala index 697e9e1f2d5e..f913b3bcae21 100644 --- a/tests/pos/t2435.scala +++ b/tests/pos/t2435.scala @@ -23,5 +23,6 @@ object Test { val a2 = a1.chain("a") println("\nDoesn't compile:") - val a = FNil.chain("a").chain("a").chain("a") + val a3 = FNil.chain("a").chain("a").chain("a") + val a4: FConstant[_ <: FConstant[_ <: FConstant[FNil.type]]] = a3 } From 91ee02fcca4881b34e35d3e2721e1b00e98fd512 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 1 Aug 2017 14:41:21 +0200 Subject: [PATCH 03/13] Make asSeenFrom use an ApproximatingTypeMap Supersedes old scheme of dealing with unstable prefixes in non-variant positions. --- .../src/dotty/tools/dotc/core/TypeOps.scala | 155 +++++++----------- .../src/dotty/tools/dotc/core/Types.scala | 66 ++++---- .../tools/dotc/printing/PlainPrinter.scala | 13 +- .../src/dotty/tools/dotc/typer/Typer.scala | 2 +- tests/neg/i1662.scala | 2 +- 5 files changed, 103 insertions(+), 135 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index 54274be4ac95..9dcaaed5dd31 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -19,122 +19,81 @@ import ast.tpd._ trait TypeOps { this: Context => // TODO: Make standalone object. /** The type `tp` as seen from prefix `pre` and owner `cls`. See the spec - * for what this means. Called very often, so the code is optimized heavily. - * - * A tricky aspect is what to do with unstable prefixes. E.g. say we have a class - * - * class C { type T; def f(x: T): T } - * - * and an expression `e` of type `C`. Then computing the type of `e.f` leads - * to the query asSeenFrom(`C`, `(x: T)T`). What should its result be? The - * naive answer `(x: C#T)C#T` is incorrect given that we treat `C#T` as the existential - * `exists(c: C)c.T`. What we need to do instead is to skolemize the existential. So - * the answer would be `(x: c.T)c.T` for some (unknown) value `c` of type `C`. - * `c.T` is expressed in the compiler as a skolem type `Skolem(C)`. - * - * Now, skolemization is messy and expensive, so we want to do it only if we absolutely - * must. Also, skolemizing immediately would mean that asSeenFrom was no longer - * idempotent - each call would return a type with a different skolem. - * Instead we produce an annotated type that marks the prefix as unsafe: - * - * (x: (C @ UnsafeNonvariant)#T)C#T - * - * We also set a global state flag `unsafeNonvariant` to the current run. - * When typing a Select node, typer will check that flag, and if it - * points to the current run will scan the result type of the select for - * @UnsafeNonvariant annotations. If it finds any, it will introduce a skolem - * constant for the prefix and try again. - * - * The scheme is efficient in particular because we expect that unsafe situations are rare; - * most compiles would contain none, so no scanning would be necessary. + * for what this means. */ final def asSeenFrom(tp: Type, pre: Type, cls: Symbol): Type = - asSeenFrom(tp, pre, cls, null) + new AsSeenFromMap(pre, cls).apply(tp) - /** Helper method, taking a map argument which is instantiated only for more - * complicated cases of asSeenFrom. - */ - private def asSeenFrom(tp: Type, pre: Type, cls: Symbol, theMap: AsSeenFromMap): Type = { - - /** Map a `C.this` type to the right prefix. If the prefix is unstable and - * the `C.this` occurs in nonvariant or contravariant position, mark the map - * to be unstable. - */ - def toPrefix(pre: Type, cls: Symbol, thiscls: ClassSymbol): Type = /*>|>*/ ctx.conditionalTraceIndented(TypeOps.track, s"toPrefix($pre, $cls, $thiscls)") /*<|<*/ { - if ((pre eq NoType) || (pre eq NoPrefix) || (cls is PackageClass)) - tp - else pre match { - case pre: SuperType => toPrefix(pre.thistpe, cls, thiscls) - case _ => - if (thiscls.derivesFrom(cls) && pre.baseTypeRef(thiscls).exists) { - if (theMap != null && theMap.currentVariance <= 0 && !isLegalPrefix(pre)) { - ctx.base.unsafeNonvariant = ctx.runId - pre match { - case AnnotatedType(_, ann) if ann.symbol == defn.UnsafeNonvariantAnnot => pre - case _ => AnnotatedType(pre, Annotation(defn.UnsafeNonvariantAnnot, Nil)) - } - } - else pre - } - else if ((pre.termSymbol is Package) && !(thiscls is Package)) - toPrefix(pre.select(nme.PACKAGE), cls, thiscls) - else - toPrefix(pre.baseTypeRef(cls).normalizedPrefix, cls.owner, thiscls) + /** The TypeMap handling the asSeenFrom */ + class AsSeenFromMap(pre: Type, cls: Symbol) extends ApproximatingTypeMap { + + def apply(tp: Type): Type = { + + /** Map a `C.this` type to the right prefix. If the prefix is unstable and + * the `C.this` occurs in nonvariant or contravariant position, mark the map + * to be unstable. + */ + def toPrefix(pre: Type, cls: Symbol, thiscls: ClassSymbol): Type = /*>|>*/ ctx.conditionalTraceIndented(TypeOps.track, s"toPrefix($pre, $cls, $thiscls)") /*<|<*/ { + if ((pre eq NoType) || (pre eq NoPrefix) || (cls is PackageClass)) + tp + else pre match { + case pre: SuperType => toPrefix(pre.thistpe, cls, thiscls) + case _ => + if (thiscls.derivesFrom(cls) && pre.baseTypeRef(thiscls).exists) + if (variance <= 0 && !isLegalPrefix(pre)) Range(pre.bottomType, pre) + else pre + else if ((pre.termSymbol is Package) && !(thiscls is Package)) + toPrefix(pre.select(nme.PACKAGE), cls, thiscls) + else + toPrefix(pre.baseTypeRef(cls).normalizedPrefix, cls.owner, thiscls) + } } - } - /*>|>*/ ctx.conditionalTraceIndented(TypeOps.track, s"asSeen ${tp.show} from (${pre.show}, ${cls.show})", show = true) /*<|<*/ { // !!! DEBUG - tp match { - case tp: NamedType => - val sym = tp.symbol - if (sym.isStatic) tp - else { - val pre1 = asSeenFrom(tp.prefix, pre, cls, theMap) - if (pre1.isUnsafeNonvariant) { - val safeCtx = ctx.withProperty(TypeOps.findMemberLimit, Some(())) - pre1.member(tp.name)(safeCtx).info match { - case TypeAlias(alias) => - // try to follow aliases of this will avoid skolemization. - return alias - case _ => + /*>|>*/ ctx.conditionalTraceIndented(TypeOps.track, s"asSeen ${tp.show} from (${pre.show}, ${cls.show})", show = true) /*<|<*/ { // !!! DEBUG + tp match { + case tp: NamedType => + val sym = tp.symbol + if (sym.isStatic) tp + else { + val pre1 = apply(tp.prefix) + if (pre1.isUnsafeNonvariant) { + val safeCtx = ctx.withProperty(TypeOps.findMemberLimit, Some(())) + pre1.member(tp.name)(safeCtx).info match { + case TypeAlias(alias) => + // try to follow aliases of this will avoid skolemization. + return alias + case _ => + } } + derivedSelect(tp, pre1) } - tp.derivedSelect(pre1) - } - case tp: ThisType => - toPrefix(pre, cls, tp.cls) - case _: BoundType | NoPrefix => - tp - case tp: RefinedType => - tp.derivedRefinedType( - asSeenFrom(tp.parent, pre, cls, theMap), - tp.refinedName, - asSeenFrom(tp.refinedInfo, pre, cls, theMap)) - case tp: TypeAlias if tp.variance == 1 => // if variance != 1, need to do the variance calculation - tp.derivedTypeAlias(asSeenFrom(tp.alias, pre, cls, theMap)) - case _ => - (if (theMap != null) theMap else new AsSeenFromMap(pre, cls)) - .mapOver(tp) + case tp: ThisType => + toPrefix(pre, cls, tp.cls) + case _: BoundType | NoPrefix => + tp + case tp: RefinedType => + derivedRefinedType(tp, apply(tp.parent), apply(tp.refinedInfo)) + case tp: TypeAlias if tp.variance == 1 => // if variance != 1, need to do the variance calculation + derivedTypeAlias(tp, apply(tp.alias)) + case _ => + mapOver(tp) + } } } + + override def reapply(tp: Type) = + // derives infos have already been subjected to asSeenFrom, hence to need to apply the map again. + tp } private def isLegalPrefix(pre: Type)(implicit ctx: Context) = pre.isStable || !ctx.phase.isTyper - /** The TypeMap handling the asSeenFrom in more complicated cases */ - class AsSeenFromMap(pre: Type, cls: Symbol) extends TypeMap { - def apply(tp: Type) = asSeenFrom(tp, pre, cls, this) - - /** A method to export the current variance of the map */ - def currentVariance = variance - } - /** Approximate a type `tp` with a type that does not contain skolem types. */ object deskolemize extends ApproximatingTypeMap { def apply(tp: Type) = /*ctx.traceIndented(i"deskolemize($tp) at $variance", show = true)*/ { tp match { - case tp: SkolemType => range(hi = atVariance(1)(apply(tp.info))) + case tp: SkolemType => range(tp.bottomType, atVariance(1)(apply(tp.info))) case _ => mapOver(tp) } } diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 9150714dbd52..efdb9b5b2988 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -283,9 +283,6 @@ object Types { /** Is this an alias TypeBounds? */ final def isAlias: Boolean = this.isInstanceOf[TypeAlias] - /** Is this a non-alias TypeBounds? */ - final def isRealTypeBounds = this.isInstanceOf[TypeBounds] && !isAlias - // ----- Higher-order combinators ----------------------------------- /** Returns true if there is a part of this type that satisfies predicate `p`. @@ -3744,18 +3741,6 @@ object Types { // of `p`'s upper bound. val prefix1 = this(tp.prefix) variance = saved - /* was: - val prefix1 = tp.info match { - case info: TypeBounds if !info.isAlias => - // prefix of an abstract type selection is non-variant, since a path - // cannot be legally widened to its underlying type, or any supertype. - val saved = variance - variance = 0 - try this(tp.prefix) finally variance = saved - case _ => - this(tp.prefix) - } - */ derivedSelect(tp, prefix1) } case _: ThisType @@ -3767,7 +3752,7 @@ object Types { case tp: TypeAlias => val saved = variance - variance = variance * tp.variance + variance *= tp.variance val alias1 = this(tp.alias) variance = saved derivedTypeAlias(tp, alias1) @@ -3902,30 +3887,29 @@ object Types { */ abstract class ApproximatingTypeMap(implicit ctx: Context) extends TypeMap { thisMap => - def range(lo: Type = defn.NothingType, hi: Type = defn.AnyType) = + protected def range(lo: Type, hi: Type) = if (variance > 0) hi else if (variance < 0) lo - else Range(loBound(lo), hiBound(hi)) + else Range(lower(lo), upper(hi)) - def isRange(tp: Type) = tp.isInstanceOf[Range] + private def isRange(tp: Type) = tp.isInstanceOf[Range] - def loBound(tp: Type) = tp match { + private def lower(tp: Type) = tp match { case tp: Range => tp.lo case _ => tp } - /** The upper bound of a TypeBounds type, the type itself otherwise */ - def hiBound(tp: Type) = tp match { + private def upper(tp: Type) = tp match { case tp: Range => tp.hi case _ => tp } - def rangeToBounds(tp: Type) = tp match { + private def rangeToBounds(tp: Type) = tp match { case Range(lo, hi) => TypeBounds(lo, hi) case _ => tp } - def atVariance[T](v: Int)(op: => T): T = { + protected def atVariance[T](v: Int)(op: => T): T = { val saved = variance variance = v try op finally variance = saved @@ -3935,11 +3919,17 @@ object Types { if (pre eq tp.prefix) tp else pre match { case Range(preLo, preHi) => - tp.info match { + preHi.member(tp.name).info match { case TypeAlias(alias) => - apply(alias) + // if H#T = U, then for any x in L..H, x.T =:= U, + // hence we can replace with U under all variances + reapply(alias) case TypeBounds(lo, hi) => - range(atVariance(-1)(apply(lo)), atVariance(1)(apply(hi))) + range(atVariance(-1)(reapply(lo)), atVariance(1)(reapply(hi))) + case info: SingletonType => + // if H#x: y.type, then for any x in L..H, x.type =:= y.type, + // hence we can replace with y.type under all variances + reapply(info) case _ => range(tp.derivedSelect(preLo), tp.derivedSelect(preHi)) } @@ -3976,12 +3966,12 @@ object Types { override protected def derivedTypeBounds(tp: TypeBounds, lo: Type, hi: Type) = if (isRange(lo) || isRange(hi)) - if (variance > 0) TypeBounds(loBound(lo), hiBound(hi)) - else range(TypeBounds(hiBound(lo), loBound(hi)), TypeBounds(loBound(lo), hiBound(hi))) + if (variance > 0) TypeBounds(lower(lo), upper(hi)) + else range(TypeBounds(upper(lo), lower(hi)), TypeBounds(lower(lo), upper(hi))) else tp.derivedTypeBounds(lo, hi) override protected def derivedSuperType(tp: SuperType, thistp: Type, supertp: Type) = - if (isRange(thistp) || isRange(supertp)) range() + if (isRange(thistp) || isRange(supertp)) range(thistp.bottomType, thistp.topType) else tp.derivedSuperType(thistp, supertp) override protected def derivedAppliedType(tp: HKApply, tycon: Type, args: List[Type]): Type = @@ -4009,15 +3999,15 @@ object Types { if (distributeArgs(args, tp.typeParams)) range(tp.derivedAppliedType(tycon, loBuf.toList), tp.derivedAppliedType(tycon, hiBuf.toList)) - else range() + else range(tp.bottomType, tp.topType) } else tp.derivedAppliedType(tycon, args) } override protected def derivedAndOrType(tp: AndOrType, tp1: Type, tp2: Type) = if (tp1.isInstanceOf[Range] || tp2.isInstanceOf[Range]) - if (tp.isAnd) range(loBound(tp1) & loBound(tp2), hiBound(tp1) & hiBound(tp2)) - else range(loBound(tp1) | loBound(tp2), hiBound(tp1) | hiBound(tp2)) + if (tp.isAnd) range(lower(tp1) & lower(tp2), upper(tp1) & upper(tp2)) + else range(lower(tp1) | lower(tp2), upper(tp1) | upper(tp2)) else tp.derivedAndOrType(tp1, tp2) override protected def derivedAnnotatedType(tp: AnnotatedType, underlying: Type, annot: Annotation) = @@ -4036,6 +4026,16 @@ object Types { assert(!pre.isInstanceOf[Range]) tp.derivedClassInfo(pre) } + + override protected def derivedLambdaType(tp: LambdaType)(formals: List[tp.PInfo], restpe: Type): Type = + restpe match { + case Range(lo, hi) => + range(derivedLambdaType(tp)(formals, lo), derivedLambdaType(tp)(formals, hi)) + case _ => + tp.derivedLambdaType(tp.paramNames, formals, restpe) + } + + protected def reapply(tp: Type): Type = apply(tp) } // ----- TypeAccumulators ---------------------------------------------------- diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index 8a10ef60ebbe..8fd557981dc7 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -68,6 +68,15 @@ class PlainPrinter(_ctx: Context) extends Printer { } else tp + private def sameBound(lo: Type, hi: Type): Boolean = + try lo =:= hi + catch { case ex: Throwable => false } + + private def homogenizeArg(tp: Type) = tp match { + case TypeBounds(lo, hi) if sameBound(lo, hi) => homogenize(hi) + case _ => tp + } + private def selfRecName(n: Int) = s"z$n" /** Render elements alternating with `sep` string */ @@ -113,9 +122,9 @@ class PlainPrinter(_ctx: Context) extends Printer { protected def toTextRefinement(rt: RefinedType) = (refinementNameString(rt) ~ toTextRHS(rt.refinedInfo)).close - protected def argText(arg: Type): Text = arg match { + protected def argText(arg: Type): Text = homogenizeArg(arg) match { case arg: TypeBounds => "_" ~ toTextGlobal(arg) - case _ => toTextGlobal(arg) + case arg => toTextGlobal(arg) } /** The longest sequence of refinement types, starting at given type diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index a389cf77e98c..b874ec5d61eb 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1427,7 +1427,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit val impl1 = cpy.Template(impl)(constr1, parents1, self1, body1) .withType(dummy.nonMemberTermRef) checkVariance(impl1) - if (!cls.is(AbstractOrTrait) && !ctx.isAfterTyper) checkRealizableBounds(cls.typeRef, cdef.namePos) + if (!cls.is(AbstractOrTrait) && !ctx.isAfterTyper) checkRealizableBounds(cls.thisType, cdef.namePos) val cdef1 = assignType(cpy.TypeDef(cdef)(name, impl1), cls) if (ctx.phase.isTyper && cdef1.tpe.derivesFrom(defn.DynamicClass) && !ctx.dynamicsEnabled) { val isRequired = parents1.exists(_.tpe.isRef(defn.DynamicClass)) diff --git a/tests/neg/i1662.scala b/tests/neg/i1662.scala index 1f9d02ba660b..722078023513 100644 --- a/tests/neg/i1662.scala +++ b/tests/neg/i1662.scala @@ -2,5 +2,5 @@ class Lift { def apply(f: F0) // error class F0 object F0 { implicit def f2f0(String): F0 = ??? } // error - (new Lift)("") + (new Lift)("") // error after switch to approximating asSeenFrom } From a26a309e29a26be319e2ce5b7dc8f5563e585483 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 1 Aug 2017 14:59:18 +0200 Subject: [PATCH 04/13] Drop UnsafeNonvariant It's no longer needed. --- .../dotty/tools/dotc/core/Definitions.scala | 2 -- .../src/dotty/tools/dotc/core/TypeOps.scala | 21 ++++++---------- .../src/dotty/tools/dotc/core/Types.scala | 14 ----------- .../src/dotty/tools/dotc/typer/Typer.scala | 25 ++----------------- .../internal/UnsafeNonvariant.scala | 8 ------ 5 files changed, 9 insertions(+), 61 deletions(-) delete mode 100644 library/src/scala/annotation/internal/UnsafeNonvariant.scala diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 452dc1a03357..62d8d57f6f47 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -657,8 +657,6 @@ class Definitions { def UncheckedStableAnnot(implicit ctx: Context) = UncheckedStableAnnotType.symbol.asClass lazy val UncheckedVarianceAnnotType = ctx.requiredClassRef("scala.annotation.unchecked.uncheckedVariance") def UncheckedVarianceAnnot(implicit ctx: Context) = UncheckedVarianceAnnotType.symbol.asClass - lazy val UnsafeNonvariantAnnotType = ctx.requiredClassRef("scala.annotation.internal.UnsafeNonvariant") - def UnsafeNonvariantAnnot(implicit ctx: Context) = UnsafeNonvariantAnnotType.symbol.asClass lazy val VolatileAnnotType = ctx.requiredClassRef("scala.volatile") def VolatileAnnot(implicit ctx: Context) = VolatileAnnotType.symbol.asClass lazy val FieldMetaAnnotType = ctx.requiredClassRef("scala.annotation.meta.field") diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index 9dcaaed5dd31..7f79cbb5048a 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -51,21 +51,14 @@ trait TypeOps { this: Context => // TODO: Make standalone object. /*>|>*/ ctx.conditionalTraceIndented(TypeOps.track, s"asSeen ${tp.show} from (${pre.show}, ${cls.show})", show = true) /*<|<*/ { // !!! DEBUG tp match { - case tp: NamedType => - val sym = tp.symbol - if (sym.isStatic) tp + case tp: NamedType => // inlined for performance; TODO: factor out into inline method + if (tp.symbol.isStatic) tp else { - val pre1 = apply(tp.prefix) - if (pre1.isUnsafeNonvariant) { - val safeCtx = ctx.withProperty(TypeOps.findMemberLimit, Some(())) - pre1.member(tp.name)(safeCtx).info match { - case TypeAlias(alias) => - // try to follow aliases of this will avoid skolemization. - return alias - case _ => - } - } - derivedSelect(tp, pre1) + val saved = variance + variance = variance max 0 + val prefix1 = this(tp.prefix) + variance = saved + derivedSelect(tp, prefix1) } case tp: ThisType => toPrefix(pre, cls, tp.cls) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index efdb9b5b2988..dce4451934c9 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -255,16 +255,6 @@ object Types { def isRepeatedParam(implicit ctx: Context): Boolean = typeSymbol eq defn.RepeatedParamClass - /** Does this type carry an UnsafeNonvariant annotation? */ - final def isUnsafeNonvariant(implicit ctx: Context): Boolean = this match { - case AnnotatedType(_, annot) => annot.symbol == defn.UnsafeNonvariantAnnot - case _ => false - } - - /** Does this type have an UnsafeNonvariant annotation on one of its parts? */ - final def hasUnsafeNonvariant(implicit ctx: Context): Boolean = - new HasUnsafeNonAccumulator().apply(false, this) - /** Is this the type of a method that has a repeated parameter type as * last parameter type? */ @@ -4175,10 +4165,6 @@ object Types { def apply(x: Unit, tp: Type): Unit = foldOver(p(tp), tp) } - class HasUnsafeNonAccumulator(implicit ctx: Context) extends TypeAccumulator[Boolean] { - def apply(x: Boolean, tp: Type) = x || tp.isUnsafeNonvariant || foldOver(x, tp) - } - class NamedPartsAccumulator(p: NamedType => Boolean, excludeLowerBounds: Boolean = false) (implicit ctx: Context) extends TypeAccumulator[mutable.Set[NamedType]] { override def stopAtStatic = false diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index b874ec5d61eb..252e9c438c26 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -386,27 +386,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit } private def typedSelect(tree: untpd.Select, pt: Type, qual: Tree)(implicit ctx: Context): Select = - healNonvariant( - checkValue(assignType(cpy.Select(tree)(qual, tree.name), qual), pt), - pt) - - /** Let `tree = p.n` where `p: T`. If tree's type is an unsafe instantiation - * (see TypeOps#asSeenFrom for how this can happen), rewrite the prefix `p` - * to `(p: )` and try again with the new (stable) - * prefix. If the result has another unsafe instantiation, raise an error. - */ - private def healNonvariant[T <: Tree](tree: T, pt: Type)(implicit ctx: Context): T = - if (ctx.unsafeNonvariant == ctx.runId && tree.tpe.widen.hasUnsafeNonvariant) - tree match { - case tree @ Select(qual, _) if !qual.tpe.isStable => - val alt = typedSelect(tree, pt, Typed(qual, TypeTree(SkolemType(qual.tpe.widen)))) - typr.println(i"healed type: ${tree.tpe} --> $alt") - alt.asInstanceOf[T] - case _ => - ctx.error(ex"unsafe instantiation of type ${tree.tpe}", tree.pos) - tree - } - else tree + checkValue(assignType(cpy.Select(tree)(qual, tree.name), qual), pt) def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = track("typedSelect") { def typeSelectOnTerm(implicit ctx: Context): Tree = { @@ -616,8 +596,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit case lhsCore: RefTree if setter.exists => val setterTypeRaw = pre.select(setterName, setter) val setterType = ensureAccessible(setterTypeRaw, isSuperSelection(lhsCore), tree.pos) - val lhs2 = healNonvariant( - untpd.rename(lhsCore, setterName).withType(setterType), WildcardType) + val lhs2 = untpd.rename(lhsCore, setterName).withType(setterType) typedUnadapted(untpd.Apply(untpd.TypedSplice(lhs2), tree.rhs :: Nil)) case _ => reassignmentToVal diff --git a/library/src/scala/annotation/internal/UnsafeNonvariant.scala b/library/src/scala/annotation/internal/UnsafeNonvariant.scala deleted file mode 100644 index b33df65d6345..000000000000 --- a/library/src/scala/annotation/internal/UnsafeNonvariant.scala +++ /dev/null @@ -1,8 +0,0 @@ -package scala.annotation.internal - -import scala.annotation.Annotation - -/** This annotation is used as a marker for unsafe - * instantiations in asSeenFrom. See TypeOps.asSeenfrom for an explanation. - */ -class UnsafeNonvariant extends Annotation From 34596279ba99c26f3c89802fe9e028a337636fff Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 1 Aug 2017 16:44:15 +0200 Subject: [PATCH 05/13] Simplifications - Use range instead of Range in AsSeenFromMap#apply. We needed Range before because we did an incorrect variance computation for NamedTypes. - Refine derivedSelect --- compiler/src/dotty/tools/dotc/core/TypeOps.scala | 2 +- compiler/src/dotty/tools/dotc/core/Types.scala | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index 7f79cbb5048a..6915a0197bab 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -40,7 +40,7 @@ trait TypeOps { this: Context => // TODO: Make standalone object. case pre: SuperType => toPrefix(pre.thistpe, cls, thiscls) case _ => if (thiscls.derivesFrom(cls) && pre.baseTypeRef(thiscls).exists) - if (variance <= 0 && !isLegalPrefix(pre)) Range(pre.bottomType, pre) + if (variance <= 0 && !isLegalPrefix(pre)) range(pre.bottomType, pre) else pre else if ((pre.termSymbol is Package) && !(thiscls is Package)) toPrefix(pre.select(nme.PACKAGE), cls, thiscls) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index dce4451934c9..5640f274f3a3 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -3915,7 +3915,9 @@ object Types { // hence we can replace with U under all variances reapply(alias) case TypeBounds(lo, hi) => - range(atVariance(-1)(reapply(lo)), atVariance(1)(reapply(hi))) + // If H#T = _ >: S <: U, then for any x in L..H, S <: x.T <: U, + // hence we can replace with S..U under all variances + range(atVariance(-variance)(reapply(lo)), reapply(hi)) case info: SingletonType => // if H#x: y.type, then for any x in L..H, x.type =:= y.type, // hence we can replace with y.type under all variances From fa79a0eeb265745019df1e06edbf51b4473227c1 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 2 Aug 2017 15:08:37 +0200 Subject: [PATCH 06/13] Refine derivedRefinedType --- .../src/dotty/tools/dotc/core/Types.scala | 34 +++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 5640f274f3a3..78bbc5e9e8c8 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -3882,19 +3882,19 @@ object Types { else if (variance < 0) lo else Range(lower(lo), upper(hi)) - private def isRange(tp: Type) = tp.isInstanceOf[Range] + protected def isRange(tp: Type) = tp.isInstanceOf[Range] - private def lower(tp: Type) = tp match { + protected def lower(tp: Type) = tp match { case tp: Range => tp.lo case _ => tp } - private def upper(tp: Type) = tp match { + protected def upper(tp: Type) = tp match { case tp: Range => tp.hi case _ => tp } - private def rangeToBounds(tp: Type) = tp match { + protected def rangeToBounds(tp: Type) = tp match { case Range(lo, hi) => TypeBounds(lo, hi) case _ => tp } @@ -3905,11 +3905,14 @@ object Types { try op finally variance = saved } + /** Derived selection. + * @pre the (upper bound of) prefix `pre` has a member named `tp.name`. + */ override protected def derivedSelect(tp: NamedType, pre: Type) = if (pre eq tp.prefix) tp else pre match { case Range(preLo, preHi) => - preHi.member(tp.name).info match { + preHi.member(tp.name).info.widenExpr match { case TypeAlias(alias) => // if H#T = U, then for any x in L..H, x.T =:= U, // hence we can replace with U under all variances @@ -3935,12 +3938,22 @@ object Types { case _ => if (parent.isBottomType) parent else info match { - case Range(infoLo, infoHi) if tp.refinedName.isTermName || variance <= 0 => - range(derivedRefinedType(tp, parent, infoLo), derivedRefinedType(tp, parent, infoHi)) + case Range(infoLo, infoHi) => + def propagate(lo: Type, hi: Type) = + range(derivedRefinedType(tp, parent, lo), derivedRefinedType(tp, parent, hi)) + tp.refinedInfo match { + case rinfo: TypeBounds => + val v = if (rinfo.isAlias) rinfo.variance * variance else variance + if (v > 0) tp.derivedRefinedType(parent, tp.refinedName, rangeToBounds(info)) + else if (v < 0) propagate(infoHi, infoLo) + else range(tp.bottomType, tp.topType) + case _ => + propagate(infoLo, infoHi) + } case _ => - tp.derivedRefinedType(parent, tp.refinedName, rangeToBounds(info)) + tp.derivedRefinedType(parent, tp.refinedName, info) } - } + } override protected def derivedRecType(tp: RecType, parent: Type) = parent match { @@ -3971,7 +3984,7 @@ object Types { case Range(tyconLo, tyconHi) => range(derivedAppliedType(tp, tyconLo, args), derivedAppliedType(tp, tyconHi, args)) case _ => - if (args.exists(isRange)) + if (args.exists(isRange)) { if (variance > 0) tp.derivedAppliedType(tycon, args.map(rangeToBounds)) else { val loBuf, hiBuf = new mutable.ListBuffer[Type] @@ -3993,6 +4006,7 @@ object Types { tp.derivedAppliedType(tycon, hiBuf.toList)) else range(tp.bottomType, tp.topType) } + } else tp.derivedAppliedType(tycon, args) } From 08de553709c53bbe82c089b430cdef2bb90a6041 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 2 Aug 2017 17:56:40 +0200 Subject: [PATCH 07/13] Fix comment in test --- tests/pos/dependent-extractors.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/pos/dependent-extractors.scala b/tests/pos/dependent-extractors.scala index c287a59c890a..4d0830155fd2 100644 --- a/tests/pos/dependent-extractors.scala +++ b/tests/pos/dependent-extractors.scala @@ -10,5 +10,5 @@ object Test { val y1: Int = y val z = (c: Any) match { case X(y) => y } - val z1: C#T = z // error: z has type Any TODO: find out why + val z1: C#T = z } From 89180b522609a0ae039249b5e12c70f7bbdd6d35 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 9 Aug 2017 10:42:54 +0200 Subject: [PATCH 08/13] Address reviewers comments --- .../src/dotty/tools/dotc/core/Types.scala | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 78bbc5e9e8c8..4ac1dc73bbcf 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -3860,14 +3860,6 @@ object Types { def apply(tp: Type) = tp } - case class Range(lo: Type, hi: Type) extends UncachedGroundType { - assert(!lo.isInstanceOf[Range]) - assert(!hi.isInstanceOf[Range]) - - override def toText(printer: Printer): Text = - lo.toText(printer) ~ ".." ~ hi.toText(printer) - } - /** A type map that approximates TypeBounds types depending on * variance. * @@ -3992,9 +3984,11 @@ object Types { case Range(lo, hi) :: args1 => val v = tparams.head.paramVariance if (v == 0) false - else if (v > 0) { loBuf += lo; hiBuf += hi } - else { loBuf += hi; hiBuf += lo } - distributeArgs(args1, tparams.tail) + else { + if (v > 0) { loBuf += lo; hiBuf += hi } + else { loBuf += hi; hiBuf += lo } + distributeArgs(args1, tparams.tail) + } case arg :: args1 => loBuf += arg; hiBuf += arg distributeArgs(args1, tparams.tail) @@ -4044,6 +4038,17 @@ object Types { protected def reapply(tp: Type): Type = apply(tp) } + /** A range of possible types between lower bound `lo` and upper bound `hi`. + * Only used internally in `ApproximatingTypeMap`. + */ + private case class Range(lo: Type, hi: Type) extends UncachedGroundType { + assert(!lo.isInstanceOf[Range]) + assert(!hi.isInstanceOf[Range]) + + override def toText(printer: Printer): Text = + lo.toText(printer) ~ ".." ~ hi.toText(printer) + } + // ----- TypeAccumulators ---------------------------------------------------- abstract class TypeAccumulator[T](implicit protected val ctx: Context) extends ((T, Type) => T) { From 8c1a6dc12bcdfe1040d179247095913e4d7c9fe4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 13 Aug 2017 12:57:34 +0200 Subject: [PATCH 09/13] Factor out variance manipulation in TypeMap and TypeAccumulator --- .../src/dotty/tools/dotc/core/Types.scala | 65 +++++++------------ 1 file changed, 24 insertions(+), 41 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 4ac1dc73bbcf..8f0f8299f62d 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -3676,14 +3676,26 @@ object Types { // ----- TypeMaps -------------------------------------------------------------------- - abstract class TypeMap(implicit protected val ctx: Context) extends (Type => Type) { thisMap => + /** Common base class of TypeMap and TypeAccumulator */ + abstract class VariantTraversal { + protected[core] var variance = 1 + + @inline protected def atVariance[T](v: Int)(op: => T): T = { + val saved = variance + variance = v + val res = op + variance = saved + res + } + } + + abstract class TypeMap(implicit protected val ctx: Context) + extends VariantTraversal with (Type => Type) { thisMap => protected def stopAtStatic = true def apply(tp: Type): Type - protected[core] var variance = 1 - protected def derivedSelect(tp: NamedType, pre: Type): Type = tp.derivedSelect(pre) protected def derivedRefinedType(tp: RefinedType, parent: Type, info: Type): Type = @@ -3721,16 +3733,13 @@ object Types { case tp: NamedType => if (stopAtStatic && tp.symbol.isStatic) tp else { - val saved = variance - variance = variance max 0 + val prefix1 = atVariance(variance max 0)(this(tp.prefix)) // A prefix is never contravariant. Even if say `p.A` is used in a contravariant // context, we cannot assume contravariance for `p` because `p`'s lower // bound might not have a binding for `A` (e.g. the lower bound could be `Nothing`). // By contrast, covariance does translate to the prefix, since we have that // if `p <: q` then `p.A <: q.A`, and well-formedness requires that `A` is a member // of `p`'s upper bound. - val prefix1 = this(tp.prefix) - variance = saved derivedSelect(tp, prefix1) } case _: ThisType @@ -3741,11 +3750,7 @@ object Types { derivedRefinedType(tp, this(tp.parent), this(tp.refinedInfo)) case tp: TypeAlias => - val saved = variance - variance *= tp.variance - val alias1 = this(tp.alias) - variance = saved - derivedTypeAlias(tp, alias1) + derivedTypeAlias(tp, atVariance(variance * tp.variance)(this(tp.alias))) case tp: TypeBounds => variance = -variance @@ -3761,12 +3766,8 @@ object Types { if (inst.exists) apply(inst) else tp case tp: HKApply => - def mapArg(arg: Type, tparam: ParamInfo): Type = { - val saved = variance - variance *= tparam.paramVariance - try this(arg) - finally variance = saved - } + def mapArg(arg: Type, tparam: ParamInfo): Type = + atVariance(variance * tparam.paramVariance)(this(arg)) derivedAppliedType(tp, this(tp.tycon), tp.args.zipWithConserve(tp.typeParams)(mapArg)) @@ -3891,12 +3892,6 @@ object Types { case _ => tp } - protected def atVariance[T](v: Int)(op: => T): T = { - val saved = variance - variance = v - try op finally variance = saved - } - /** Derived selection. * @pre the (upper bound of) prefix `pre` has a member named `tp.name`. */ @@ -4051,7 +4046,8 @@ object Types { // ----- TypeAccumulators ---------------------------------------------------- - abstract class TypeAccumulator[T](implicit protected val ctx: Context) extends ((T, Type) => T) { + abstract class TypeAccumulator[T](implicit protected val ctx: Context) + extends VariantTraversal with ((T, Type) => T) { protected def stopAtStatic = true @@ -4059,15 +4055,8 @@ object Types { protected def applyToAnnot(x: T, annot: Annotation): T = x // don't go into annotations - protected var variance = 1 - - protected final def applyToPrefix(x: T, tp: NamedType) = { - val saved = variance - variance = variance max 0 // see remark on NamedType case in TypeMap - val result = this(x, tp.prefix) - variance = saved - result - } + protected final def applyToPrefix(x: T, tp: NamedType) = + atVariance(variance max 0)(this(x, tp.prefix)) // see remark on NamedType case in TypeMap def foldOver(x: T, tp: Type): T = tp match { case tp: TypeRef => @@ -4088,13 +4077,7 @@ object Types { this(this(x, tp.parent), tp.refinedInfo) case bounds @ TypeBounds(lo, hi) => - if (lo eq hi) { - val saved = variance - variance = variance * bounds.variance - val result = this(x, lo) - variance = saved - result - } + if (lo eq hi) atVariance(variance * bounds.variance)(this(x, lo)) else { variance = -variance val y = this(x, lo) From 41609219feeb721ed06c2d30bba5bca52ac0e5a3 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 13 Aug 2017 18:10:03 +0200 Subject: [PATCH 10/13] Fix derivedRefinedType in ApproximatingTypeMap The logic before was overcomplicated since I did not take into account that Range refinedInfos could only happen at variance 0. On the othet hand, it did not take into account all the subtleties of alias types with variances. --- .../src/dotty/tools/dotc/core/Types.scala | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 8f0f8299f62d..dd1500154aa4 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -3923,20 +3923,27 @@ object Types { case Range(parentLo, parentHi) => range(derivedRefinedType(tp, parentLo, info), derivedRefinedType(tp, parentHi, info)) case _ => + def propagate(lo: Type, hi: Type) = + range(derivedRefinedType(tp, parent, lo), derivedRefinedType(tp, parent, hi)) if (parent.isBottomType) parent else info match { + case Range(infoLo: TypeBounds, infoHi: TypeBounds) => + assert(variance == 0) + val v1 = infoLo.variance + val v2 = infoHi.variance + // There's some weirdness coming from the way aliases can have variance + // If infoLo and infoHi are both aliases with the same non-zero variance + // we can propagate to a range of the refined types. If they are both + // non-alias ranges we know that infoLo <:< infoHi and therefore we can + // propagate to refined types with infoLo and infoHi as bounds. + // In all other cases, Nothing..Any is the only interval that contains + // the range. i966.scala is a test case. + if (v1 > 0 && v2 > 0) propagate(infoLo, infoHi) + else if (v1 < 0 && v2 < 0) propagate(infoHi, infoLo) + else if (!infoLo.isAlias && !infoHi.isAlias) propagate(infoLo, infoHi) + else range(tp.bottomType, tp.topType) case Range(infoLo, infoHi) => - def propagate(lo: Type, hi: Type) = - range(derivedRefinedType(tp, parent, lo), derivedRefinedType(tp, parent, hi)) - tp.refinedInfo match { - case rinfo: TypeBounds => - val v = if (rinfo.isAlias) rinfo.variance * variance else variance - if (v > 0) tp.derivedRefinedType(parent, tp.refinedName, rangeToBounds(info)) - else if (v < 0) propagate(infoHi, infoLo) - else range(tp.bottomType, tp.topType) - case _ => - propagate(infoLo, infoHi) - } + propagate(infoLo, infoHi) case _ => tp.derivedRefinedType(parent, tp.refinedName, info) } From 396acbf01e4d6cb884c100929f618dc76a5524ce Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 13 Aug 2017 19:33:21 +0200 Subject: [PATCH 11/13] Add comment explaining approximating derivedAppliedType --- compiler/src/dotty/tools/dotc/core/Types.scala | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index dd1500154aa4..6c772a009145 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -3982,6 +3982,13 @@ object Types { if (variance > 0) tp.derivedAppliedType(tycon, args.map(rangeToBounds)) else { val loBuf, hiBuf = new mutable.ListBuffer[Type] + // Given `C[A1, ..., An]` where sone A's are ranges, try to find + // non-range arguments L1, ..., Ln and H1, ..., Hn such that + // C[L1, ..., Ln] <: C[H1, ..., Hn] by taking the right limits of + // ranges that appear in as co- or contravariant arguments. + // Fail for non-variant argument ranges. + // If successful, the L-arguments are in loBut, the H-arguments in hiBuf. + // @return operation succeeded for all arguments. def distributeArgs(args: List[Type], tparams: List[ParamInfo]): Boolean = args match { case Range(lo, hi) :: args1 => val v = tparams.head.paramVariance From e332bd358137f08cbe4d675359d052e8715616e7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 13 Aug 2017 19:36:39 +0200 Subject: [PATCH 12/13] Polishings and more comments --- compiler/src/dotty/tools/dotc/core/Types.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 6c772a009145..70fa176c5794 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -4014,7 +4014,7 @@ object Types { } override protected def derivedAndOrType(tp: AndOrType, tp1: Type, tp2: Type) = - if (tp1.isInstanceOf[Range] || tp2.isInstanceOf[Range]) + if (isRange(tp1) || isRange(tp2)) if (tp.isAnd) range(lower(tp1) & lower(tp2), upper(tp1) & upper(tp2)) else range(lower(tp1) | lower(tp2), upper(tp1) | upper(tp2)) else tp.derivedAndOrType(tp1, tp2) @@ -4032,7 +4032,9 @@ object Types { } override protected def derivedClassInfo(tp: ClassInfo, pre: Type): Type = { - assert(!pre.isInstanceOf[Range]) + assert(!isRange(pre)) + // we don't know what to do here; this case has to be handled in subclasses + // (typically by handling ClassInfo's specially, in case they can be encountered). tp.derivedClassInfo(pre) } From efbb1c6526d5eb4ae90eade11510ce5d9113dcd6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 13 Aug 2017 19:43:02 +0200 Subject: [PATCH 13/13] Fix review comments re homogenizedArg --- compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index 8fd557981dc7..add58d0d73ad 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -12,6 +12,7 @@ import typer.ImportInfo import config.Config import java.lang.Integer.toOctalString import config.Config.summarizeDepth +import scala.util.control.NonFatal import scala.annotation.switch class PlainPrinter(_ctx: Context) extends Printer { @@ -69,11 +70,11 @@ class PlainPrinter(_ctx: Context) extends Printer { else tp private def sameBound(lo: Type, hi: Type): Boolean = - try lo =:= hi - catch { case ex: Throwable => false } + try ctx.typeComparer.isSameTypeWhenFrozen(lo, hi) + catch { case NonFatal(ex) => false } private def homogenizeArg(tp: Type) = tp match { - case TypeBounds(lo, hi) if sameBound(lo, hi) => homogenize(hi) + case TypeBounds(lo, hi) if homogenizedView && sameBound(lo, hi) => homogenize(hi) case _ => tp }