From bae7ed30597ce7e152417e85f38e38574dab9844 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 31 Jul 2017 17:53:15 +0200 Subject: [PATCH 01/23] 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 ca7f63ee12a3..7b5a02a51661 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -139,6 +139,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 => @@ -278,7 +284,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 ----------------------------------- @@ -1223,6 +1232,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. @@ -1769,6 +1790,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 @@ -3725,6 +3747,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 @@ -3854,63 +3888,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 34a3fe2ab102f4105b9241bfc38377ddb2cd03b1 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 24 Jul 2017 18:02:36 +0200 Subject: [PATCH 02/23] 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 7b5a02a51661..8b48fc5006f5 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -3891,6 +3891,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 @@ -3925,14 +3928,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) } @@ -3950,11 +3962,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) => @@ -3962,11 +3976,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) @@ -4006,6 +4022,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) => @@ -4017,6 +4034,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 31d9a83175ad3e4d073ecb0b248d319b13a2273e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 1 Aug 2017 14:41:21 +0200 Subject: [PATCH 03/23] 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 8b48fc5006f5..740ac5f12efa 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -286,9 +286,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`. @@ -3747,18 +3744,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 @@ -3770,7 +3755,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) @@ -3905,30 +3890,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 @@ -3938,11 +3922,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)) } @@ -3979,12 +3969,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 = @@ -4012,15 +4002,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) = @@ -4039,6 +4029,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 d33c09ee8cf4..a6ccf63bc29b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1428,7 +1428,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 f130c7238f5132ee2c6fc43e6a96ff9c341b01dd Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 1 Aug 2017 14:59:18 +0200 Subject: [PATCH 04/23] 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 740ac5f12efa..a80f4f182880 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -258,16 +258,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? */ @@ -4178,10 +4168,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 a6ccf63bc29b..2e723b381ee0 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -387,27 +387,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 = { @@ -617,8 +597,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 135be6b465057d60d3b4c579099e427295f786c4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 1 Aug 2017 16:44:15 +0200 Subject: [PATCH 05/23] 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 a80f4f182880..b38774b04f4b 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -3918,7 +3918,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 3922954586eccf8c29fe64774390eaa0a19ed26b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 2 Aug 2017 15:08:37 +0200 Subject: [PATCH 06/23] 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 b38774b04f4b..4d88e9cefe24 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -3885,19 +3885,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 } @@ -3908,11 +3908,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 @@ -3938,12 +3941,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 { @@ -3974,7 +3987,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] @@ -3996,6 +4009,7 @@ object Types { tp.derivedAppliedType(tycon, hiBuf.toList)) else range(tp.bottomType, tp.topType) } + } else tp.derivedAppliedType(tycon, args) } From 0db70ab195dd06987d62e41bbbf83b5974242699 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 2 Aug 2017 17:56:40 +0200 Subject: [PATCH 07/23] 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 c5b57f1c4b04af263688936e3b174f8507000587 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 9 Aug 2017 10:42:54 +0200 Subject: [PATCH 08/23] 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 4d88e9cefe24..c892ddc161a4 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -3863,14 +3863,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. * @@ -3995,9 +3987,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) @@ -4047,6 +4041,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 47967b208fae7ec386d3a649b59976643c1407b3 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 13 Aug 2017 12:57:34 +0200 Subject: [PATCH 09/23] 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 c892ddc161a4..b0497e70d641 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,12 +3895,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`. */ @@ -4054,7 +4049,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 @@ -4062,15 +4058,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 => @@ -4091,13 +4080,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 0dae33cec226abd19bc28f951d7fc0fe2de50900 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 13 Aug 2017 18:10:03 +0200 Subject: [PATCH 10/23] 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 b0497e70d641..f9c80f2ea454 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -3926,20 +3926,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 1440b256344132f9e1db43bfe1e5f3001bb1e23f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 13 Aug 2017 19:33:21 +0200 Subject: [PATCH 11/23] 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 f9c80f2ea454..865c468737dd 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -3985,6 +3985,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 742a2d945d6e6c64c04814a2feb559bfbadc7468 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 13 Aug 2017 19:36:39 +0200 Subject: [PATCH 12/23] 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 865c468737dd..8e56838569f1 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -4017,7 +4017,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) @@ -4035,7 +4035,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 b9be4547641489228c82cc2cd16092caf746144e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 13 Aug 2017 19:43:02 +0200 Subject: [PATCH 13/23] 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 } From 3264fb5fade8301396e1face8c87b11c84af9ec0 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 2 Aug 2017 17:50:52 +0200 Subject: [PATCH 14/23] Implement avoid in terms of ApproximatingTypeMap This puts avoid on firmer ground. --- .../dotty/tools/dotc/typer/TypeAssigner.scala | 89 ++++++++++++++++--- 1 file changed, 79 insertions(+), 10 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index 42cee77cb899..493b992fe9c5 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -39,18 +39,84 @@ trait TypeAssigner { } } + /** Given a class info, the intersection of its parents, refined by all + * non-private fields, methods, and type members. + */ + def classBound(info: ClassInfo)(implicit ctx: Context): Type = { + val parentType = info.parentsWithArgs.reduceLeft(ctx.typeComparer.andType(_, _)) + def addRefinement(parent: Type, decl: Symbol) = { + val inherited = + parentType.findMember(decl.name, info.cls.thisType, Private) + .suchThat(decl.matches(_)) + val inheritedInfo = inherited.info + if (inheritedInfo.exists && decl.info <:< inheritedInfo && !(inheritedInfo <:< decl.info)) { + val r = RefinedType(parent, decl.name, decl.info) + typr.println(i"add ref $parent $decl --> " + r) + r + } + else + parent + } + val refinableDecls = info.decls.filter( + sym => !(sym.is(TypeParamAccessor | Private) || sym.isConstructor)) + val raw = (parentType /: refinableDecls)(addRefinement) + RecType.closeOver(rt => raw.substThis(info.cls, RecThis(rt))) + } + /** An upper approximation of the given type `tp` that does not refer to any symbol in `symsToAvoid`. - * Approximation steps are: - * - * - follow aliases and upper bounds if the original refers to a forbidden symbol - * - widen termrefs that refer to a forbidden symbol - * - replace ClassInfos of forbidden classes by the intersection of their parents, refined by all - * non-private fields, methods, and type members. - * - if the prefix of a class refers to a forbidden symbol, first try to replace the prefix, - * if this is not possible, replace the ClassInfo as above. - * - drop refinements referring to a forbidden symbol. */ def avoid(tp: Type, symsToAvoid: => List[Symbol])(implicit ctx: Context): Type = { + val wmap = new ApproximatingTypeMap { + lazy val forbidden = symsToAvoid.toSet + def toAvoid(sym: Symbol) = !sym.isStatic && forbidden.contains(sym) + def partsToAvoid = new NamedPartsAccumulator(tp => toAvoid(tp.symbol)) + def apply(tp: Type): Type = tp match { + case tp: TermRef + if toAvoid(tp.symbol) || partsToAvoid(mutable.Set.empty, tp.info).nonEmpty => + tp.info.widenExpr match { + case info: SingletonType => apply(info) + case info => range(tp.info.bottomType, apply(info)) + } + case tp: TypeRef if toAvoid(tp.symbol) => + val avoided = tp.info match { + case TypeAlias(alias) => + apply(alias) + case TypeBounds(lo, hi) => + range(atVariance(-variance)(apply(lo)), apply(hi)) + case info: ClassInfo => + range(tp.bottomType, apply(classBound(info))) + 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) => + val lo = ctx.typeComparer.instanceType(tp.origin, fromBelow = true) + val lo1 = apply(lo) + //println(i"INST $tp --> $lo --> $lo1") + if (lo1 ne lo) lo1 else tp + case tp: TermRef if false => + val saved = variance + variance = 0 + val prefix1 = this(tp.prefix) + variance = saved + if (isRange(prefix1)) range(tp.bottomType, apply(tp.info.widenExpr)) + else derivedSelect(tp, prefix1) + case _ => + mapOver(tp) + } + + /** Needs to handle the case where the prefix type does not have a member + * named `tp.name` anymmore. + */ + 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) + } val widenMap = new TypeMap { lazy val forbidden = symsToAvoid.toSet def toAvoid(tp: Type): Boolean = @@ -128,7 +194,10 @@ trait TypeAssigner { mapOver(tp) } } - widenMap(tp) + //val was = widenMap(tp) + val now = wmap(tp) + //if (was.show != now.show) println(i"difference for avoid $tp, ${tp.toString}, forbidden = $symsToAvoid%, %, was: $was, now: $now") + now } def avoidingType(expr: Tree, bindings: List[Tree])(implicit ctx: Context): Type = From ec3f7f45561a6319a903da477f1b2fe7a8a5dc84 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 2 Aug 2017 18:49:53 +0200 Subject: [PATCH 15/23] Cleanups - Add documentation - Drop debug code - Tweak what we do in the TypeVar case to align with interpolation. --- .../dotty/tools/dotc/typer/TypeAssigner.scala | 112 +++--------------- 1 file changed, 19 insertions(+), 93 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index 493b992fe9c5..3641915b53e7 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -64,9 +64,19 @@ trait TypeAssigner { } /** An upper approximation of the given type `tp` that does not refer to any symbol in `symsToAvoid`. + * We need to approximate with ranges: + * + * term references to symbols in `symsToAvoid`, + * term references that have a widened type of which some part refers + * to a symbol in `symsToAvoid`, + * type references to symbols in `symsToAvoid`, + * this types of classes in `symsToAvoid`. + * + * Type variables that would be interpolated to a type that + * needs to be widened are replaced by the widened interpolation instance. */ def avoid(tp: Type, symsToAvoid: => List[Symbol])(implicit ctx: Context): Type = { - val wmap = new ApproximatingTypeMap { + val widenMap = new ApproximatingTypeMap { lazy val forbidden = symsToAvoid.toSet def toAvoid(sym: Symbol) = !sym.isStatic && forbidden.contains(sym) def partsToAvoid = new NamedPartsAccumulator(tp => toAvoid(tp.symbol)) @@ -92,23 +102,18 @@ trait TypeAssigner { case tp: ThisType if toAvoid(tp.cls) => range(tp.bottomType, apply(classBound(tp.cls.classInfo))) case tp: TypeVar if ctx.typerState.constraint.contains(tp) => - val lo = ctx.typeComparer.instanceType(tp.origin, fromBelow = true) + val lo = ctx.typeComparer.instanceType(tp.origin, fromBelow = variance >= 0) val lo1 = apply(lo) - //println(i"INST $tp --> $lo --> $lo1") if (lo1 ne lo) lo1 else tp - case tp: TermRef if false => - val saved = variance - variance = 0 - val prefix1 = this(tp.prefix) - variance = saved - if (isRange(prefix1)) range(tp.bottomType, apply(tp.info.widenExpr)) - else derivedSelect(tp, prefix1) case _ => mapOver(tp) } - /** Needs to handle the case where the prefix type does not have a member - * named `tp.name` anymmore. + /** Two deviations from standard derivedSelect: + * 1. The teh approximation result is a singleton references 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. */ override def derivedSelect(tp: NamedType, pre: Type) = if (pre eq tp.prefix) tp @@ -117,87 +122,8 @@ trait TypeAssigner { else if (upper(pre).member(tp.name).exists) super.derivedSelect(tp, pre) else range(tp.bottomType, tp.topType) } - val widenMap = new TypeMap { - lazy val forbidden = symsToAvoid.toSet - def toAvoid(tp: Type): Boolean = - // TODO: measure the cost of using `existsPart`, and if necessary replace it - // by a `TypeAccumulator` where we have set `stopAtStatic = true`. - tp existsPart { - case tp: TermRef => forbidden.contains(tp.symbol) || toAvoid(tp.underlying) - case tp: TypeRef => forbidden.contains(tp.symbol) - case tp: ThisType => forbidden.contains(tp.cls) - case _ => false - } - def apply(tp: Type): Type = tp match { - case tp: TermRef - if toAvoid(tp) && (variance > 0 || tp.info.widenExpr <:< tp) => - // Can happen if `x: y.type`, then `x.type =:= y.type`, hence we can widen `x.type` - // to y.type in all contexts, not just covariant ones. - apply(tp.info.widenExpr) - case tp: TypeRef if toAvoid(tp) => - tp.info match { - case TypeAlias(ref) => - apply(ref) - case info: ClassInfo if variance > 0 => - if (!(forbidden contains tp.symbol)) { - val prefix = apply(tp.prefix) - val tp1 = tp.derivedSelect(prefix) - if (tp1.typeSymbol.exists) - return tp1 - } - val parentType = info.parentsWithArgs.reduceLeft(ctx.typeComparer.andType(_, _)) - def addRefinement(parent: Type, decl: Symbol) = { - val inherited = - parentType.findMember(decl.name, info.cls.thisType, Private) - .suchThat(decl.matches(_)) - val inheritedInfo = inherited.info - if (inheritedInfo.exists && decl.info <:< inheritedInfo && !(inheritedInfo <:< decl.info)) { - val r = RefinedType(parent, decl.name, decl.info) - typr.println(i"add ref $parent $decl --> " + r) - r - } - else - parent - } - val refinableDecls = info.decls.filter( - sym => !(sym.is(TypeParamAccessor | Private) || sym.isConstructor)) - val fullType = (parentType /: refinableDecls)(addRefinement) - apply(fullType) - case TypeBounds(lo, hi) if variance > 0 => - apply(hi) - case _ => - mapOver(tp) - } - case tp @ HKApply(tycon, args) if toAvoid(tycon) => - apply(tp.superType) - case tp @ AppliedType(tycon, args) if toAvoid(tycon) => - val base = apply(tycon) - var args = tp.baseArgInfos(base.typeSymbol) - if (base.typeParams.length != args.length) - args = base.typeParams.map(_.paramInfo) - apply(base.appliedTo(args)) - case tp @ RefinedType(parent, name, rinfo) if variance > 0 => - val parent1 = apply(tp.parent) - val refinedInfo1 = apply(rinfo) - if (toAvoid(refinedInfo1)) { - typr.println(s"dropping refinement from $tp") - if (name.isTypeName) tp.derivedRefinedType(parent1, name, TypeBounds.empty) - else parent1 - } else { - tp.derivedRefinedType(parent1, name, refinedInfo1) - } - case tp: TypeVar if ctx.typerState.constraint.contains(tp) => - val lo = ctx.typerState.constraint.fullLowerBound(tp.origin) - val lo1 = avoid(lo, symsToAvoid) - if (lo1 ne lo) lo1 else tp - case _ => - mapOver(tp) - } - } - //val was = widenMap(tp) - val now = wmap(tp) - //if (was.show != now.show) println(i"difference for avoid $tp, ${tp.toString}, forbidden = $symsToAvoid%, %, was: $was, now: $now") - now + + widenMap(tp) } def avoidingType(expr: Tree, bindings: List[Tree])(implicit ctx: Context): Type = From aff87e0282d12c413793f6320ad9ea8da359abe4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 4 Aug 2017 09:21:00 +0200 Subject: [PATCH 16/23] Some opimizations on derived ops In a commonly used derived... operation, deal with the common case that the types have not chanegd first. --- compiler/src/dotty/tools/dotc/core/Types.scala | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 8e56838569f1..f2ae16bb1295 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -3922,7 +3922,8 @@ object Types { } override protected def derivedRefinedType(tp: RefinedType, parent: Type, info: Type) = - parent match { + if ((parent eq tp.parent) && (info eq tp.refinedInfo)) tp + else parent match { case Range(parentLo, parentHi) => range(derivedRefinedType(tp, parentLo, info), derivedRefinedType(tp, parentHi, info)) case _ => @@ -3953,13 +3954,15 @@ object Types { } override protected def derivedRecType(tp: RecType, parent: Type) = - parent match { + if (parent eq tp.parent) tp + else 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 { + if (alias eq tp.alias) tp + else alias match { case Range(lo, hi) => if (variance > 0) TypeBounds(lo, hi) else range(TypeAlias(lo), TypeAlias(hi)) @@ -3967,7 +3970,8 @@ object Types { } override protected def derivedTypeBounds(tp: TypeBounds, lo: Type, hi: Type) = - if (isRange(lo) || isRange(hi)) + if ((lo eq tp.lo) && (hi eq tp.hi)) tp + else if (isRange(lo) || isRange(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) From d2a2b3ee894aab701f6106bf8cb29cf0e1bbfe7b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 14 Aug 2017 10:17:37 +0200 Subject: [PATCH 17/23] Address reviewers comments --- .../src/dotty/tools/dotc/core/TypeOps.scala | 18 ++++++------------ compiler/src/dotty/tools/dotc/core/Types.scala | 4 ++++ .../dotty/tools/dotc/typer/TypeAssigner.scala | 5 ++--- 3 files changed, 12 insertions(+), 15 deletions(-) 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 f2ae16bb1295..034b50d98a69 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -3946,6 +3946,9 @@ object Types { 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 to a 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) => propagate(infoLo, infoHi) case _ => @@ -4015,6 +4018,7 @@ 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) diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index 3641915b53e7..4340cae22ca6 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) => From cfaca26a1f9da80c948bc8f7a2e00a5045429742 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 14 Aug 2017 15:01:35 +0200 Subject: [PATCH 18/23] Drop unused field `unsafeNonvariant` --- compiler/src/dotty/tools/dotc/core/Contexts.scala | 6 ------ 1 file changed, 6 deletions(-) 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. From 1d6726bf06b92770cf35fdb0a7209416a9ccd896 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 14 Aug 2017 15:03:20 +0200 Subject: [PATCH 19/23] Fix typo --- compiler/src/dotty/tools/dotc/core/Types.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 034b50d98a69..8f1933a2f8b2 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -3947,7 +3947,7 @@ object Types { 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 to a hk-types, which is not what we want. + // 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) => propagate(infoLo, infoHi) From 0cd8e50c1be55d57afbcf083caeed659bcf6ee9a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 15 Aug 2017 15:15:53 +0200 Subject: [PATCH 20/23] Fix derivedSelect in avoid Test case in i2945.scala. --- .../src/dotty/tools/dotc/core/Types.scala | 30 +++++++++++++------ .../dotty/tools/dotc/typer/TypeAssigner.scala | 26 ++++++++++------ tests/pos/i2945.scala | 13 ++++++++ 3 files changed, 51 insertions(+), 18 deletions(-) create mode 100644 tests/pos/i2945.scala diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 8f1933a2f8b2..724363be41d4 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -3895,14 +3895,14 @@ object Types { case _ => tp } - /** 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 @@ -3916,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) = diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index 4340cae22ca6..fe3b80f3417f 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -108,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/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]` +} From 45ffdf0dc19070628a4e6eb93946e4ab7ed04b8a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 15 Aug 2017 15:17:25 +0200 Subject: [PATCH 21/23] Avoid double avoidance when typing blocks For a Block, TypeAssigner does an avoid just for term symbols whereas Typer does one for all local symbols. Since Typer called TypeAssigner, we got a double avoidance pass. This is eliminated by assigning types directly in Typer without passing through TypeAssigner. --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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] = { From 4a5edb27494e38b7d5f5ead811c9cef96fbe2c12 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 2 Aug 2017 18:31:26 +0200 Subject: [PATCH 22/23] Fix #2928: Add special mode for computing the type of a LHS To fix #2928, we need to compute the member type of an assigment's left-hand side using negative variance. --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 9883a3f0f006..7b8d3bd218ba 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -587,8 +587,13 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit lhsCore.tpe match { case ref: TermRef => val lhsVal = lhsCore.denot.suchThat(!_.is(Method)) - if (canAssign(lhsVal.symbol)) - assignType(cpy.Assign(tree)(lhs1, typed(tree.rhs, lhsVal.info))) + if (canAssign(lhsVal.symbol)) { + // lhsBounds: (T .. Any) as seen from lhs prefix, where T is the type of lhsVal.symbol + // This ensures we do the as-seen-from on T with variance -1. Test case neg/i2928.scala + val lhsBounds = + TypeBounds.lower(lhsVal.symbol.info).asSeenFrom(ref.prefix, lhsVal.symbol.owner) + assignType(cpy.Assign(tree)(lhs1, typed(tree.rhs, lhsBounds.loBound))) + } else { val pre = ref.prefix val setterName = ref.name.setterName From 4f624fc8d869955a4b9e4f35df4f7146680c260d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 2 Aug 2017 19:19:29 +0200 Subject: [PATCH 23/23] Add test case --- tests/neg/i2928.scala | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 tests/neg/i2928.scala diff --git a/tests/neg/i2928.scala b/tests/neg/i2928.scala new file mode 100644 index 000000000000..7b2b54a7f091 --- /dev/null +++ b/tests/neg/i2928.scala @@ -0,0 +1,15 @@ +class Box[T22](var v: T22) + +object Test { + def main(args: Array[String]): Unit = { + val s = new Box[String]("") + val i = new Box[Int](3) + + var box: Box[_] = s + val sv = box.v + box = i + box.v = sv // error + + val c: Int = i.v + } +}