diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index 97624aab777a..a3c2ffec202c 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -631,12 +631,6 @@ object Contexts { * of underlying during a controlled operation exists. */ private[core] val pendingUnderlying = new mutable.HashSet[Type] - /** A flag that some unsafe nonvariant instantiation was encountered - * in this run. Used as a shortcut to a avoid scans of types in - * Typer.typedSelect. - */ - private[dotty] var unsafeNonvariant: RunId = NoRunId - /** A map from ErrorType to associated message computation. We use this map * instead of storing message computations directly in ErrorTypes in order * to avoid space leaks - the message computation usually captures a context. diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index 6915a0197bab..8fbda8daf7b5 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -29,10 +29,9 @@ trait TypeOps { this: Context => // TODO: Make standalone object. 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. - */ + /** Map a `C.this` type to the right prefix. If the prefix is unstable, and + * the current variance is <= 0, return a range. + */ 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 @@ -50,16 +49,11 @@ trait TypeOps { this: Context => // TODO: Make standalone object. } /*>|>*/ ctx.conditionalTraceIndented(TypeOps.track, s"asSeen ${tp.show} from (${pre.show}, ${cls.show})", show = true) /*<|<*/ { // !!! DEBUG + // One `case ThisType` is specific to asSeenFrom, all other cases are inlined for performance tp match { - case tp: NamedType => // inlined for performance; TODO: factor out into inline method + case tp: NamedType => if (tp.symbol.isStatic) tp - else { - val saved = variance - variance = variance max 0 - val prefix1 = this(tp.prefix) - variance = saved - derivedSelect(tp, prefix1) - } + else derivedSelect(tp, atVariance(variance max 0)(this(tp.prefix))) case tp: ThisType => toPrefix(pre, cls, tp.cls) case _: BoundType | NoPrefix => diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index fafffd41f355..724363be41d4 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -3679,14 +3679,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 = @@ -3724,16 +3736,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 @@ -3744,11 +3753,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 @@ -3764,12 +3769,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)) @@ -3894,20 +3895,14 @@ 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`. + /** Try to widen a named type to its info relative to given prefix `pre`, where possible. + * The possible cases are listed inline in the code. Return `default` if no widening is + * possible. */ - 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.widenExpr match { + def tryWiden(tp: NamedType, pre: Type)(default: => Type): Type = + pre.member(tp.name) match { + case d: SingleDenotation => + d.info 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 @@ -3921,9 +3916,21 @@ object Types { // hence we can replace with y.type under all variances reapply(info) case _ => - range(tp.derivedSelect(preLo), tp.derivedSelect(preHi)) + default } - case _ => tp.derivedSelect(pre) + case _ => default + } + + /** 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) => + tryWiden(tp, preHi)(range(tp.derivedSelect(preLo), tp.derivedSelect(preHi))) + case _ => + tp.derivedSelect(pre) } override protected def derivedRefinedType(tp: RefinedType, parent: Type, info: Type) = @@ -3932,20 +3939,30 @@ 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) + // Using `parent` instead of `tp.topType` would be better for normal refinements, + // but it would also turn *-types into hk-types, which is not what we want. + // We should revisit this point in case we represent applied types not as refinements anymore. 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) } @@ -3987,6 +4004,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 @@ -4006,13 +4030,14 @@ object Types { range(tp.derivedAppliedType(tycon, loBuf.toList), tp.derivedAppliedType(tycon, hiBuf.toList)) else range(tp.bottomType, tp.topType) + // TODO: can we give a better bound than `topType`? } } else tp.derivedAppliedType(tycon, args) } 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) @@ -4030,7 +4055,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) } @@ -4058,7 +4085,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 @@ -4066,15 +4094,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 => @@ -4095,13 +4116,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) 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 } diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index 3641915b53e7..fe3b80f3417f 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -46,7 +46,7 @@ trait TypeAssigner { val parentType = info.parentsWithArgs.reduceLeft(ctx.typeComparer.andType(_, _)) def addRefinement(parent: Type, decl: Symbol) = { val inherited = - parentType.findMember(decl.name, info.cls.thisType, Private) + parentType.findMember(decl.name, info.cls.thisType, excluded = Private) .suchThat(decl.matches(_)) val inheritedInfo = inherited.info if (inheritedInfo.exists && decl.info <:< inheritedInfo && !(inheritedInfo <:< decl.info)) { @@ -88,7 +88,7 @@ trait TypeAssigner { case info => range(tp.info.bottomType, apply(info)) } case tp: TypeRef if toAvoid(tp.symbol) => - val avoided = tp.info match { + tp.info match { case TypeAlias(alias) => apply(alias) case TypeBounds(lo, hi) => @@ -98,7 +98,6 @@ trait TypeAssigner { case _ => range(tp.bottomType, tp.topType) // should happen only in error cases } - avoided case tp: ThisType if toAvoid(tp.cls) => range(tp.bottomType, apply(classBound(tp.cls.classInfo))) case tp: TypeVar if ctx.typerState.constraint.contains(tp) => @@ -109,18 +108,26 @@ trait TypeAssigner { mapOver(tp) } - /** Two deviations from standard derivedSelect: - * 1. The teh approximation result is a singleton references C#x.type, we + /** Three deviations from standard derivedSelect: + * 1. We first try a widening conversion to the type's info with + * the original prefix. Since the original prefix is known to + * be a subtype of the returned prefix, this can improve results. + * 2. IThen, if the approximation result is a singleton reference C#x.type, we * replace by the widened type, which is usually more natural. - * 2. We need to handle the case where the prefix type does not have a member - * named `tp.name` anymmore. + * 3. Finally, we need to handle the case where the prefix type does not have a member + * named `tp.name` anymmore. In that case, we need to fall back to Bot..Top. */ override def derivedSelect(tp: NamedType, pre: Type) = - if (pre eq tp.prefix) tp - else if (tp.isTerm && variance > 0 && !pre.isInstanceOf[SingletonType]) - apply(tp.info.widenExpr) - else if (upper(pre).member(tp.name).exists) super.derivedSelect(tp, pre) - else range(tp.bottomType, tp.topType) + if (pre eq tp.prefix) + tp + else tryWiden(tp, tp.prefix) { + if (tp.isTerm && variance > 0 && !pre.isInstanceOf[SingletonType]) + apply(tp.info.widenExpr) + else if (upper(pre).member(tp.name).exists) + super.derivedSelect(tp, pre) + else + range(tp.bottomType, tp.topType) + } } widenMap(tp) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 2e723b381ee0..9883a3f0f006 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -624,7 +624,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit else pt.notApplied val expr1 = typedExpr(tree.expr, ept)(exprCtx) ensureNoLocalRefs( - assignType(cpy.Block(tree)(stats1, expr1), stats1, expr1), pt, localSyms(stats1)) + cpy.Block(tree)(stats1, expr1).withType(expr1.tpe), pt, localSyms(stats1)) } def escapingRefs(block: Tree, localSyms: => List[Symbol])(implicit ctx: Context): collection.Set[NamedType] = { diff --git a/tests/pos/i2945.scala b/tests/pos/i2945.scala new file mode 100644 index 000000000000..a8d4af597ba0 --- /dev/null +++ b/tests/pos/i2945.scala @@ -0,0 +1,13 @@ +object Test { + def test = { + object Hi { + type A = Int + } + + val x: Hi.A = 1 + + List(x) + } + + val hi: List[Int] = test // Used to fail because `test` had type `List[Any]` instead of `List[Int]` +}