From 8e9233b9fce1c7e15d0d1111e88edd5139b3ced7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 23 May 2015 14:46:28 +0200 Subject: Tighten comparison of skolem types Skolem types are =:= only if they are reference-equal. Two skolem types with the same underlying type are not necessarily equal. Tests continue to run under this tightened definition. --- src/dotty/tools/dotc/core/TypeComparer.scala | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index 069525db0..9559666a4 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -274,11 +274,6 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi case _ => thirdTry(tp1, tp2) } - case tp1: SkolemType => - tp2 match { - case tp2: SkolemType if tp1 == tp2 => true - case _ => thirdTry(tp1, tp2) - } case tp1: TypeVar => isSubType(tp1.underlying, tp2) case tp1: WildcardType => -- cgit v1.2.3 From c8a479255745d7de391f386bbf8946233ff46f7d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 24 May 2015 11:43:37 +0200 Subject: Make skolemsstate three valued --- src/dotty/tools/dotc/core/Skolemization.scala | 23 +++++++++++++++++------ src/dotty/tools/dotc/core/TypeComparer.scala | 7 ++++--- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/dotty/tools/dotc/core/Skolemization.scala b/src/dotty/tools/dotc/core/Skolemization.scala index fb47cb62a..af7530458 100644 --- a/src/dotty/tools/dotc/core/Skolemization.scala +++ b/src/dotty/tools/dotc/core/Skolemization.scala @@ -18,26 +18,33 @@ trait Skolemization { implicit val ctx: Context - protected var skolemsOutstanding = false + private[core] var skolemsState = Skolemization.SkolemsDisallowed - def ensureStableSingleton(tp: Type): SingletonType = tp.stripTypeVar match { + final def ensureStableSingleton(tp: Type): SingletonType = tp.stripTypeVar match { case tp: SingletonType if tp.isStable => tp case tp: ValueType => - skolemsOutstanding = true + assert(skolemsState != Skolemization.SkolemsDisallowed) + skolemsState = Skolemization.SkolemsEncountered SkolemType(tp) case tp: TypeProxy => ensureStableSingleton(tp.underlying) } - /** Approximate a type `tp` with a type that does not contain skolem types. + /** If skolems were encountered, approximate a type `tp` with a type that + * does not contain skolem types. * @param toSuper if true, return the smallest supertype of `tp` with this property * else return the largest subtype. */ - final def deSkolemize(tp: Type, toSuper: Boolean): Type = - if (skolemsOutstanding) deSkolemize(tp, if (toSuper) 1 else -1, Set()) + final def deSkolemizeIfSkolemsSeen(tp: Type, toSuper: Boolean): Type = + if (skolemsState == Skolemization.SkolemsEncountered) + deSkolemize(tp, if (toSuper) 1 else -1, Set()) else tp + /** Approximate a type `tp` with a type that does not contain skolem types. + */ + final def deSkolemize(tp: Type): Type = deSkolemize(tp, 1, Set()) + private def deSkolemize(tp: Type, variance: Int, seen: Set[SkolemType]): Type = ctx.traceIndented(s"deskolemize $tp, variance = $variance, seen = $seen = ") { def approx(lo: Type = defn.NothingType, hi: Type = defn.AnyType, newSeen: Set[SkolemType] = seen) = @@ -124,3 +131,7 @@ trait Skolemization { } } } + +object Skolemization extends Enumeration { + val SkolemsDisallowed, SkolemsAllowed, SkolemsEncountered = Value +} diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index 9559666a4..692f05d5a 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -531,7 +531,8 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi * rebase both itself and the member info of `tp` on a freshly created skolem type. */ protected def hasMatchingMember(name: Name, tp1: Type, tp2: RefinedType): Boolean = { - val saved = skolemsOutstanding + val saved = skolemsState + if (skolemsState == Skolemization.SkolemsDisallowed) skolemsState = Skolemization.SkolemsAllowed try { val rebindNeeded = tp2.refinementRefersToThis val base = if (rebindNeeded) ensureStableSingleton(tp1) else tp1 @@ -555,7 +556,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi } } } - finally skolemsOutstanding = saved + finally skolemsState = saved } /** Skip refinements in `tp2` which match corresponding refinements in `tp1`. @@ -640,7 +641,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi private def narrowGADTBounds(tr: NamedType, bound: Type, isUpper: Boolean): Boolean = ctx.mode.is(Mode.GADTflexible) && { val tparam = tr.symbol - val bound1 = deSkolemize(bound, toSuper = !isUpper) + val bound1 = deSkolemizeIfSkolemsSeen(bound, toSuper = !isUpper) typr.println(s"narrow gadt bound of $tparam: ${tparam.info} from ${if (isUpper) "above" else "below"} to $bound1 ${bound1.isRef(tparam)}") !bound1.isRef(tparam) && { val oldBounds = ctx.gadt.bounds(tparam) -- cgit v1.2.3 From 3352ffc97f3577fd6de5c22a22c7c7c887e9b1f9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 24 May 2015 13:50:32 +0200 Subject: Tighten isStable predicate A term ref is stable only if its prefix is also stable. At the same time, we drop stability requirements where they no longer make sense (e.g. in isLegalPrefix). --- src/dotty/tools/dotc/core/Types.scala | 14 +------------- src/dotty/tools/dotc/typer/Checking.scala | 7 ------- src/dotty/tools/dotc/typer/Typer.scala | 8 +++----- tests/neg/projections.scala | 7 ------- 4 files changed, 4 insertions(+), 32 deletions(-) delete mode 100644 tests/neg/projections.scala diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index ae9088f00..8825f396d 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -90,7 +90,7 @@ object Types { /** Does this type denote a stable reference (i.e. singleton type)? */ final def isStable(implicit ctx: Context): Boolean = this match { - case tp: TermRef => tp.termSymbol.isStable + case tp: TermRef => tp.termSymbol.isStable && tp.prefix.isStable case _: SingletonType => true case NoPrefix => true case _ => false @@ -154,18 +154,6 @@ object Types { false } - /** A type T is a legal prefix in a type selection T#A if - * T is stable or T contains no abstract types except possibly A. - * !!! Todo: What about non-final vals that contain abstract types? - */ - final def isLegalPrefixFor(selector: Name)(implicit ctx: Context): Boolean = - isStable || { - val absTypeNames = memberNames(abstractTypeNameFilter) - if (absTypeNames.nonEmpty) typr.println(s"abstract type members of ${this.showWithUnderlying()}: $absTypeNames") - absTypeNames.isEmpty || - absTypeNames.head == selector && absTypeNames.tail.isEmpty - } - /** Is this type guaranteed not to have `null` as a value? * For the moment this is only true for modules, but it could * be refined later. diff --git a/src/dotty/tools/dotc/typer/Checking.scala b/src/dotty/tools/dotc/typer/Checking.scala index b2d368e8c..3ef6d059a 100644 --- a/src/dotty/tools/dotc/typer/Checking.scala +++ b/src/dotty/tools/dotc/typer/Checking.scala @@ -252,12 +252,6 @@ trait Checking { if (!tp.isStable && !tp.isErroneous) ctx.error(d"$tp is not stable", pos) - /** Check that type `tp` is a legal prefix for '#'. - * @return The type itself - */ - def checkLegalPrefix(tp: Type, selector: Name, pos: Position)(implicit ctx: Context): Unit = - if (!tp.isLegalPrefixFor(selector)) ctx.error(d"$tp is not a valid prefix for '# $selector'", pos) - /** Check that `tp` is a class type with a stable prefix. Also, if `traitReq` is * true check that `tp` is a trait. * Stability checking is disabled in phases after RefChecks. @@ -345,7 +339,6 @@ trait NoChecking extends Checking { override def checkValue(tree: Tree, proto: Type)(implicit ctx: Context): tree.type = tree override def checkBounds(args: List[tpd.Tree], poly: PolyType)(implicit ctx: Context): Unit = () override def checkStable(tp: Type, pos: Position)(implicit ctx: Context): Unit = () - override def checkLegalPrefix(tp: Type, selector: Name, pos: Position)(implicit ctx: Context): Unit = () override def checkClassTypeWithStablePrefix(tp: Type, pos: Position, traitReq: Boolean)(implicit ctx: Context): Type = tp override def checkImplicitParamsNotSingletons(vparamss: List[List[ValDef]])(implicit ctx: Context): Unit = () override def checkFeasible(tp: Type, pos: Position, where: => String = "")(implicit ctx: Context): Type = tp diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index acf4f3845..fd1d034fd 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -312,13 +312,12 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit if (ctx.compilationUnit.isJava && tree.name.isTypeName) { // SI-3120 Java uses the same syntax, A.B, to express selection from the // value A and from the type A. We have to try both. - tryEither(tryCtx => asSelect(tryCtx))((_,_) => asJavaSelectFromTypeTree(ctx)) + tryEither(tryCtx => asSelect(tryCtx))((_, _) => asJavaSelectFromTypeTree(ctx)) } else asSelect(ctx) } def typedSelectFromTypeTree(tree: untpd.SelectFromTypeTree, pt: Type)(implicit ctx: Context): Tree = track("typedSelectFromTypeTree") { val qual1 = typedType(tree.qualifier, selectionProto(tree.name, pt, this)) - checkLegalPrefix(qual1.tpe, tree.name, qual1.pos) assignType(cpy.SelectFromTypeTree(tree)(qual1, tree.name), qual1) } @@ -347,8 +346,8 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit val clsDef = TypeDef(x, templ).withFlags(Final) typed(cpy.Block(tree)(clsDef :: Nil, New(Ident(x), Nil)), pt) case _ => - val tpt1 = typedType(tree.tpt) - checkClassTypeWithStablePrefix(tpt1.tpe, tpt1.pos, traitReq = false) + val tpt1 = typedType(tree.tpt) + checkClassTypeWithStablePrefix(tpt1.tpe, tpt1.pos, traitReq = false) assignType(cpy.New(tree)(tpt1), tpt1) // todo in a later phase: checkInstantiatable(cls, tpt1.pos) } @@ -524,7 +523,6 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit */ var fnBody = tree.body - /** If function is of the form * (x1, ..., xN) => f(x1, ..., XN) * the type of `f`, otherwise NoType. (updates `fnBody` as a side effect). diff --git a/tests/neg/projections.scala b/tests/neg/projections.scala deleted file mode 100644 index 5d80e1151..000000000 --- a/tests/neg/projections.scala +++ /dev/null @@ -1,7 +0,0 @@ -class projections { - - class Lambda { type Arg; type Apply } - - var x: (Lambda { type Apply = Int }) # Apply = _ // error: illegal prefix - -} -- cgit v1.2.3 From bf203a52ef0933498a88c2c60e4dad6005bf51cb Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 26 May 2015 12:03:02 +0200 Subject: Follow aliases when deskolemizing Be more aggressive doing this than with lookupRefined in that we compute the member of a projected name, instead of just analyzing the type structurally. Reason: (1) If we do not follow aliases, skolemization will lose information (2) Skolemization is applied rather late, less risk of cyclic references by computing members. --- src/dotty/tools/dotc/core/Skolemization.scala | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/dotty/tools/dotc/core/Skolemization.scala b/src/dotty/tools/dotc/core/Skolemization.scala index af7530458..eef5f0e5d 100644 --- a/src/dotty/tools/dotc/core/Skolemization.scala +++ b/src/dotty/tools/dotc/core/Skolemization.scala @@ -1,7 +1,7 @@ package dotty.tools.dotc package core -import Symbols._, Types._, Contexts._ +import Symbols._, Types._, Contexts._, Decorators._ import collection.mutable /** Methods to add and remove skolemtypes. @@ -46,7 +46,7 @@ trait Skolemization { final def deSkolemize(tp: Type): Type = deSkolemize(tp, 1, Set()) private def deSkolemize(tp: Type, variance: Int, seen: Set[SkolemType]): Type = - ctx.traceIndented(s"deskolemize $tp, variance = $variance, seen = $seen = ") { + ctx.traceIndented(i"deskolemize $tp, variance = $variance, seen = $seen = ", show = true) { def approx(lo: Type = defn.NothingType, hi: Type = defn.AnyType, newSeen: Set[SkolemType] = seen) = if (variance == 0) NoType else deSkolemize(if (variance < 0) lo else hi, variance, newSeen) @@ -59,12 +59,20 @@ trait Skolemization { if (sym.isStatic) tp else { val pre1 = deSkolemize(tp.prefix, variance, seen) - if (pre1.exists && !pre1.isRef(defn.NothingClass)) tp.derivedSelect(pre1) + if (pre1 eq tp.prefix) tp else { - ctx.log(s"deskolem: $tp: ${tp.info}") - tp.info match { - case TypeBounds(lo, hi) => approx(lo, hi) - case info => approx(defn.NothingType, info) + val d = tp.prefix.member(tp.name) + d.info match { + case TypeAlias(alias) => deSkolemize(alias, variance, seen) + case _ => + if (pre1.exists && !pre1.isRef(defn.NothingClass)) tp.derivedSelect(pre1) + else { + ctx.log(s"deskolem: $tp: ${tp.info}") + tp.info match { + case TypeBounds(lo, hi) => approx(lo, hi) + case info => approx(defn.NothingType, info) + } + } } } } -- cgit v1.2.3 From 3d241f3ddbb1d364d163612f643adb57b355f554 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 26 May 2015 12:04:38 +0200 Subject: More inclusive isStable test. (1) Refinements of stable types are stable (2) TypeVars instantiated to stable types are stable. --- src/dotty/tools/dotc/core/Types.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 8825f396d..6b46e475b 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -89,9 +89,10 @@ object Types { final def isValueType: Boolean = this.isInstanceOf[ValueType] /** Does this type denote a stable reference (i.e. singleton type)? */ - final def isStable(implicit ctx: Context): Boolean = this match { + final def isStable(implicit ctx: Context): Boolean = stripTypeVar match { case tp: TermRef => tp.termSymbol.isStable && tp.prefix.isStable case _: SingletonType => true + case tp: RefinedType => tp.parent.isStable case NoPrefix => true case _ => false } -- cgit v1.2.3 From eaf3095494af2b0ea028452450a6af44e27f5284 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 26 May 2015 13:19:04 +0200 Subject: Disable deskolemization in narrowGADTBounds Will use deskolemize later, when method result types are inferred. --- src/dotty/tools/dotc/core/TypeComparer.scala | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index 692f05d5a..e90c30025 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -641,13 +641,12 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi private def narrowGADTBounds(tr: NamedType, bound: Type, isUpper: Boolean): Boolean = ctx.mode.is(Mode.GADTflexible) && { val tparam = tr.symbol - val bound1 = deSkolemizeIfSkolemsSeen(bound, toSuper = !isUpper) - typr.println(s"narrow gadt bound of $tparam: ${tparam.info} from ${if (isUpper) "above" else "below"} to $bound1 ${bound1.isRef(tparam)}") - !bound1.isRef(tparam) && { + typr.println(s"narrow gadt bound of $tparam: ${tparam.info} from ${if (isUpper) "above" else "below"} to $bound ${bound.isRef(tparam)}") + !bound.isRef(tparam) && { val oldBounds = ctx.gadt.bounds(tparam) val newBounds = - if (isUpper) TypeBounds(oldBounds.lo, oldBounds.hi & bound1) - else TypeBounds(oldBounds.lo | bound1, oldBounds.hi) + if (isUpper) TypeBounds(oldBounds.lo, oldBounds.hi & bound) + else TypeBounds(oldBounds.lo | bound, oldBounds.hi) isSubType(newBounds.lo, newBounds.hi) && { ctx.gadt.setBounds(tparam, newBounds); true } } -- cgit v1.2.3 From 080ed2f3d8b5d8fd249691aacc0fe4f9596bb772 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 5 Jun 2015 18:49:36 +0200 Subject: Split RefinedThis and SkolemType SkolemTypes need to behave differently form RefinedThis types in TypeMap and TypeAccumulator. For skolem types these should follow through to the underlying type. For RefinedThis types, these need to do nothing, in order not to start an infinite recursion. --- src/dotty/tools/dotc/core/Skolemization.scala | 4 +- src/dotty/tools/dotc/core/Substituters.scala | 18 +++---- src/dotty/tools/dotc/core/TypeApplications.scala | 8 +-- src/dotty/tools/dotc/core/TypeComparer.scala | 2 +- src/dotty/tools/dotc/core/TypeOps.scala | 3 ++ src/dotty/tools/dotc/core/Types.scala | 61 ++++++++++++++++------ src/dotty/tools/dotc/core/tasty/TastyFormat.scala | 23 ++++---- src/dotty/tools/dotc/core/tasty/TreePickler.scala | 5 +- .../tools/dotc/core/tasty/TreeUnpickler.scala | 2 + .../dotc/core/unpickleScala2/Scala2Unpickler.scala | 2 +- src/dotty/tools/dotc/printing/PlainPrinter.scala | 9 ++-- src/dotty/tools/dotc/printing/RefinedPrinter.scala | 2 +- src/dotty/tools/dotc/typer/Typer.scala | 2 +- 13 files changed, 88 insertions(+), 53 deletions(-) diff --git a/src/dotty/tools/dotc/core/Skolemization.scala b/src/dotty/tools/dotc/core/Skolemization.scala index eef5f0e5d..8baf612ba 100644 --- a/src/dotty/tools/dotc/core/Skolemization.scala +++ b/src/dotty/tools/dotc/core/Skolemization.scala @@ -30,7 +30,7 @@ trait Skolemization { case tp: TypeProxy => ensureStableSingleton(tp.underlying) } - +/*@@@ /** If skolems were encountered, approximate a type `tp` with a type that * does not contain skolem types. * @param toSuper if true, return the smallest supertype of `tp` with this property @@ -137,7 +137,7 @@ trait Skolemization { this.seen = savedSeen } } - } + }*/ } object Skolemization extends Enumeration { diff --git a/src/dotty/tools/dotc/core/Substituters.scala b/src/dotty/tools/dotc/core/Substituters.scala index 77ecf7fba..e4bbf2305 100644 --- a/src/dotty/tools/dotc/core/Substituters.scala +++ b/src/dotty/tools/dotc/core/Substituters.scala @@ -179,21 +179,21 @@ trait Substituters { this: Context => .mapOver(tp) } - final def substSkolem(tp: Type, from: Type, to: Type, theMap: SubstSkolemMap): Type = + final def substRefinedThis(tp: Type, from: Type, to: Type, theMap: SubstRefinedThisMap): Type = tp match { - case tp @ SkolemType(binder) => + case tp @ RefinedThis(binder) => if (binder eq from) to else tp case tp: NamedType => if (tp.currentSymbol.isStatic) tp - else tp.derivedSelect(substSkolem(tp.prefix, from, to, theMap)) + else tp.derivedSelect(substRefinedThis(tp.prefix, from, to, theMap)) case _: ThisType | _: BoundType | NoPrefix => tp case tp: RefinedType => - tp.derivedRefinedType(substSkolem(tp.parent, from, to, theMap), tp.refinedName, substSkolem(tp.refinedInfo, from, to, theMap)) + tp.derivedRefinedType(substRefinedThis(tp.parent, from, to, theMap), tp.refinedName, substRefinedThis(tp.refinedInfo, from, to, theMap)) case tp: TypeAlias => - tp.derivedTypeAlias(substSkolem(tp.alias, from, to, theMap)) + tp.derivedTypeAlias(substRefinedThis(tp.alias, from, to, theMap)) case _ => - (if (theMap != null) theMap else new SubstSkolemMap(from, to)) + (if (theMap != null) theMap else new SubstRefinedThisMap(from, to)) .mapOver(tp) } @@ -222,7 +222,7 @@ trait Substituters { this: Context => case tp: NamedType => if (tp.currentSymbol.isStatic) tp else tp.derivedSelect(substParams(tp.prefix, from, to, theMap)) - case _: ThisType | NoPrefix | _: SkolemType => + case _: ThisType | NoPrefix => tp case tp: RefinedType => tp.derivedRefinedType(substParams(tp.parent, from, to, theMap), tp.refinedName, substParams(tp.refinedInfo, from, to, theMap)) @@ -266,8 +266,8 @@ trait Substituters { this: Context => def apply(tp: Type): Type = substThis(tp, from, to, this) } - final class SubstSkolemMap(from: Type, to: Type) extends DeepTypeMap { - def apply(tp: Type): Type = substSkolem(tp, from, to, this) + final class SubstRefinedThisMap(from: Type, to: Type) extends DeepTypeMap { + def apply(tp: Type): Type = substRefinedThis(tp, from, to, this) } final class SubstParamMap(from: ParamType, to: Type) extends DeepTypeMap { diff --git a/src/dotty/tools/dotc/core/TypeApplications.scala b/src/dotty/tools/dotc/core/TypeApplications.scala index 7f3f8a446..f466cee77 100644 --- a/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/src/dotty/tools/dotc/core/TypeApplications.scala @@ -387,9 +387,9 @@ class TypeApplications(val self: Type) extends AnyVal { case _ => firstBaseArgInfo(defn.SeqClass) } - def containsSkolemType(target: Type)(implicit ctx: Context): Boolean = { + def containsRefinedThis(target: Type)(implicit ctx: Context): Boolean = { def recur(tp: Type): Boolean = tp.stripTypeVar match { - case SkolemType(tp) => + case RefinedThis(tp) => tp eq target case tp: NamedType => tp.info match { @@ -446,7 +446,7 @@ class TypeApplications(val self: Type) extends AnyVal { def replacements(rt: RefinedType): List[Type] = for (sym <- boundSyms) - yield TypeRef(SkolemType(rt), correspondingParamName(sym)) + yield TypeRef(RefinedThis(rt), correspondingParamName(sym)) def rewrite(tp: Type): Type = tp match { case tp @ RefinedType(parent, name: TypeName) => @@ -489,7 +489,7 @@ class TypeApplications(val self: Type) extends AnyVal { val lambda = defn.lambdaTrait(boundSyms.map(_.variance)) val substitutedRHS = (rt: RefinedType) => { val argRefs = boundSyms.indices.toList.map(i => - SkolemType(rt).select(tpnme.lambdaArgName(i))) + RefinedThis(rt).select(tpnme.lambdaArgName(i))) tp.subst(boundSyms, argRefs).bounds.withVariance(1) } val res = RefinedType(lambda.typeRef, tpnme.Apply, substitutedRHS) diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index e90c30025..64faa6d93 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -536,7 +536,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi try { val rebindNeeded = tp2.refinementRefersToThis val base = if (rebindNeeded) ensureStableSingleton(tp1) else tp1 - val rinfo2 = if (rebindNeeded) tp2.refinedInfo.substSkolem(tp2, base) else tp2.refinedInfo + val rinfo2 = if (rebindNeeded) tp2.refinedInfo.substRefinedThis(tp2, base) else tp2.refinedInfo def qualifies(m: SingleDenotation) = isSubType(m.info, rinfo2) def memberMatches(mbr: Denotation): Boolean = mbr match { // inlined hasAltWith for performance case mbr: SingleDenotation => qualifies(mbr) diff --git a/src/dotty/tools/dotc/core/TypeOps.scala b/src/dotty/tools/dotc/core/TypeOps.scala index acbd5b6f0..a01f8af9f 100644 --- a/src/dotty/tools/dotc/core/TypeOps.scala +++ b/src/dotty/tools/dotc/core/TypeOps.scala @@ -12,6 +12,9 @@ import ast.tpd._ trait TypeOps { this: Context => // TODO: Make standalone object. + final def asSeenFrom(tp: Type, pre: Type, cls: Symbol): Type = + asSeenFrom(tp, pre, cls, null) + final def asSeenFrom(tp: Type, pre: Type, cls: Symbol, theMap: AsSeenFromMap): Type = { def toPrefix(pre: Type, cls: Symbol, thiscls: ClassSymbol): Type = /*>|>*/ ctx.conditionalTraceIndented(TypeOps.track, s"toPrefix($pre, $cls, $thiscls)") /*<|<*/ { diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 6b46e475b..109f39c2f 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -48,6 +48,7 @@ object Types { * | | +--- SuperType * | | +--- ConstantType * | | +--- MethodParam + * | | +----RefinedThis * | | +--- SkolemType * | +- PolyParam * | +- RefinedType @@ -438,7 +439,7 @@ object Types { def goRefined(tp: RefinedType) = { val pdenot = go(tp.parent) val rinfo = - if (tp.refinementRefersToThis) tp.refinedInfo.substSkolem(tp, pre) + if (tp.refinementRefersToThis) tp.refinedInfo.substRefinedThis(tp, pre) else tp.refinedInfo if (name.isTypeName) { // simplified case that runs more efficiently val jointInfo = @@ -583,7 +584,7 @@ object Types { */ final def asSeenFrom(pre: Type, cls: Symbol)(implicit ctx: Context): Type = track("asSeenFrom") { if (!cls.membersNeedAsSeenFrom(pre)) this - else ctx.asSeenFrom(this, pre, cls, null) + else ctx.asSeenFrom(this, pre, cls) } // ----- Subtype-related -------------------------------------------- @@ -822,7 +823,7 @@ object Types { object instantiate extends TypeMap { var isSafe = true def apply(tp: Type): Type = tp match { - case TypeRef(SkolemType(`pre`), name) if name.isLambdaArgName => + case TypeRef(RefinedThis(`pre`), name) if name.isLambdaArgName => val TypeAlias(alias) = member(name).info alias case tp: TypeVar if !tp.inst.exists => @@ -845,13 +846,15 @@ object Types { if (pre.refinedName ne name) loop(pre.parent, pre.refinedName :: resolved) else if (!pre.refinementRefersToThis) alias else alias match { - case TypeRef(SkolemType(`pre`), aliasName) => lookupRefined(aliasName) // (1) + case TypeRef(RefinedThis(`pre`), aliasName) => lookupRefined(aliasName) // (1) case _ => if (name == tpnme.Apply) betaReduce(alias) else NoType // (2) } case _ => loop(pre.parent, resolved) } - case SkolemType(binder) => + case RefinedThis(binder) => binder.lookupRefined(name) + case SkolemType(tp) => + tp.lookupRefined(name) case pre: WildcardType => WildcardType case pre: TypeRef => @@ -1024,8 +1027,8 @@ object Types { if (cls.isStaticOwner) this else ctx.substThis(this, cls, tp, null) /** Substitute all occurrences of `SkolemType(binder)` by `tp` */ - final def substSkolem(binder: Type, tp: Type)(implicit ctx: Context): Type = - ctx.substSkolem(this, binder, tp, null) + final def substRefinedThis(binder: Type, tp: Type)(implicit ctx: Context): Type = + ctx.substRefinedThis(this, binder, tp, null) /** Substitute a bound type by some other type */ final def substParam(from: ParamType, to: Type)(implicit ctx: Context): Type = @@ -1402,7 +1405,7 @@ object Types { * to an (unbounded) wildcard type. * * (2) Reduce a type-ref `T { X = U; ... } # X` to `U` - * provided `U` does not refer with a SkolemType to the + * provided `U` does not refer with a RefinedThis to the * refinement type `T { X = U; ... }` */ def reduceProjection(implicit ctx: Context): Type = { @@ -1816,7 +1819,7 @@ object Types { def refinementRefersToThis(implicit ctx: Context): Boolean = { if (!refinementRefersToThisKnown) { - refinementRefersToThisCache = refinedInfo.containsSkolemType(this) + refinementRefersToThisCache = refinedInfo.containsRefinedThis(this) refinementRefersToThisKnown = true } refinementRefersToThisCache @@ -1852,7 +1855,7 @@ object Types { derivedRefinedType(parent.EtaExpand, refinedName, refinedInfo) else if (false) RefinedType(parent, refinedName, refinedInfo) - else RefinedType(parent, refinedName, rt => refinedInfo.substSkolem(this, SkolemType(rt))) + else RefinedType(parent, refinedName, rt => refinedInfo.substRefinedThis(this, RefinedThis(rt))) } /** Add this refinement to `parent`, provided If `refinedName` is a member of `parent`. */ @@ -2236,7 +2239,7 @@ object Types { } } - // ----- Bound types: MethodParam, PolyParam, SkolemType -------------------------- + // ----- Bound types: MethodParam, PolyParam, RefinedThis -------------------------- abstract class BoundType extends CachedProxyType with ValueType { type BT <: Type @@ -2309,20 +2312,38 @@ object Types { } } - /** A skolem type reference with underlying type `binder`. */ - case class SkolemType(binder: Type) extends BoundType with SingletonType { - type BT = Type + /** a this-reference to an enclosing refined type `binder`. */ + case class RefinedThis(binder: RefinedType) extends BoundType with SingletonType { + type BT = RefinedType override def underlying(implicit ctx: Context) = binder - def copyBoundType(bt: BT) = SkolemType(bt) + def copyBoundType(bt: BT) = RefinedThis(bt) // need to customize hashCode and equals to prevent infinite recursion for // refinements that refer to the refinement type via this override def computeHash = addDelta(binder.identityHash, 41) override def equals(that: Any) = that match { - case that: SkolemType => this.binder eq that.binder + case that: RefinedThis => this.binder eq that.binder case _ => false } - override def toString = s"SkolemType(${binder.hashCode})" + override def toString = s"RefinedThis(${binder.hashCode})" + } + + // ----- Skolem types ----------------------------------------------- + + /** A skolem type reference with underlying type `binder`. */ + abstract case class SkolemType(info: Type) extends CachedProxyType with ValueType with SingletonType { + override def underlying(implicit ctx: Context) = info + def derivedSkolemType(info: Type)(implicit ctx: Context) = + if (info eq this.info) this else SkolemType(info) + override def computeHash = doHash(info) + override def toString = s"Skolem($info)" + } + + final class CachedSkolemType(info: Type) extends SkolemType(info) + + object SkolemType { + def apply(info: Type)(implicit ctx: Context) = + unique(new CachedSkolemType(info)) } // ------------ Type variables ---------------------------------------- @@ -2883,6 +2904,9 @@ object Types { case tp: AndOrType => tp.derivedAndOrType(this(tp.tp1), this(tp.tp2)) + case tp: SkolemType => + tp.derivedSkolemType(this(tp.info)) + case tp @ AnnotatedType(annot, underlying) => val underlying1 = this(underlying) if (underlying1 eq underlying) tp else tp.derivedAnnotatedType(mapOver(annot), underlying1) @@ -3022,6 +3046,9 @@ object Types { case tp: AndOrType => this(this(x, tp.tp1), tp.tp2) + case tp: SkolemType => + this(x, tp.info) + case AnnotatedType(annot, underlying) => this(applyToAnnot(x, annot), underlying) diff --git a/src/dotty/tools/dotc/core/tasty/TastyFormat.scala b/src/dotty/tools/dotc/core/tasty/TastyFormat.scala index 106a6510d..1022fc4da 100644 --- a/src/dotty/tools/dotc/core/tasty/TastyFormat.scala +++ b/src/dotty/tools/dotc/core/tasty/TastyFormat.scala @@ -103,10 +103,10 @@ Standard-Section: "ASTs" TopLevelStat* TERMREFpkg fullyQualified_NameRef TERMREF possiblySigned_NameRef qual_Type THIS clsRef_Type - SKOLEMtype refinedType_ASTRef + REFINEDthis refinedType_ASTRef + SKOLEMtype Type_ASTRef SHARED path_ASTRef - Constant = UNITconst FALSEconst TRUEconst @@ -262,15 +262,16 @@ object TastyFormat { final val TERMREFpkg = 67 final val TYPEREFpkg = 68 final val SKOLEMtype = 69 - final val BYTEconst = 70 - final val SHORTconst = 71 - final val CHARconst = 72 - final val INTconst = 73 - final val LONGconst = 74 - final val FLOATconst = 75 - final val DOUBLEconst = 76 - final val STRINGconst = 77 - final val IMPORTED = 78 + final val REFINEDthis = 70 + final val BYTEconst = 71 + final val SHORTconst = 72 + final val CHARconst = 73 + final val INTconst = 74 + final val LONGconst = 75 + final val FLOATconst = 76 + final val DOUBLEconst = 77 + final val STRINGconst = 78 + final val IMPORTED = 79 final val THIS = 96 final val CLASSconst = 97 diff --git a/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 365b5d268..cd49f7c5a 100644 --- a/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -198,9 +198,12 @@ class TreePickler(pickler: TastyPickler) { case tpe: SuperType => writeByte(SUPERtype) withLength { pickleType(tpe.thistpe); pickleType(tpe.supertpe)} + case tpe: RefinedThis => + writeByte(REFINEDthis) + writeRef(pickledTypes.get(tpe.binder).asInstanceOf[Addr]) case tpe: SkolemType => writeByte(SKOLEMtype) - writeRef(pickledTypes.get(tpe.binder).asInstanceOf[Addr]) + pickleType(tpe.info) case tpe: RefinedType => val args = tpe.argInfos(interpolate = false) if (args.isEmpty) { diff --git a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index e753bdcab..d4260e679 100644 --- a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -254,6 +254,8 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { } case THIS => ThisType.raw(readType().asInstanceOf[TypeRef]) + case REFINEDthis => + RefinedThis(readTypeRef().asInstanceOf[RefinedType]) case SKOLEMtype => SkolemType(readTypeRef()) case SHARED => diff --git a/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala b/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala index b4549a8d8..9498cf43c 100644 --- a/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala +++ b/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala @@ -689,7 +689,7 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas else { def addRefinement(tp: Type, sym: Symbol) = { def subst(info: Type, rt: RefinedType) = - if (clazz.isClass) info.substThis(clazz.asClass, SkolemType(rt)) + if (clazz.isClass) info.substThis(clazz.asClass, RefinedThis(rt)) else info // turns out some symbols read into `clazz` are not classes, not sure why this is the case. RefinedType(tp, sym.name, subst(sym.info, _)) } diff --git a/src/dotty/tools/dotc/printing/PlainPrinter.scala b/src/dotty/tools/dotc/printing/PlainPrinter.scala index 12c94677f..de1a439cf 100644 --- a/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -228,11 +228,10 @@ class PlainPrinter(_ctx: Context) extends Printer { toText(value) case MethodParam(mt, idx) => nameString(mt.paramNames(idx)) - case sk: SkolemType => - sk.binder match { - case rt: RefinedType => s"${nameString(rt.typeSymbol)}{...}.this" - case _ => "" - } + case tp: RefinedThis => + s"${nameString(tp.binder.typeSymbol)}{...}.this" + case tp: SkolemType => + "" // !!! todo refine with unique identifier. } } diff --git a/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/src/dotty/tools/dotc/printing/RefinedPrinter.scala index fa238f32c..cf80969bf 100644 --- a/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -190,7 +190,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { // LambdaI{...}.HK$i val simplifyArgs = new TypeMap { override def apply(tp: Type) = tp match { - case tp @ TypeRef(SkolemType(_), name) if name.isLambdaArgName => + case tp @ TypeRef(RefinedThis(_), name) if name.isLambdaArgName => TypeRef(NoPrefix, tp.symbol.asType) case _ => mapOver(tp) diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index fd1d034fd..5f03d19e7 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -812,7 +812,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit if ((rsym.is(Method) || rsym.isType) && rsym.allOverriddenSymbols.isEmpty) ctx.error(i"refinement $rsym without matching type in parent $parent", refinement.pos) val rinfo = if (rsym is Accessor) rsym.info.resultType else rsym.info - RefinedType(parent, rsym.name, rt => rinfo.substThis(refineCls, SkolemType(rt))) + RefinedType(parent, rsym.name, rt => rinfo.substThis(refineCls, RefinedThis(rt))) // todo later: check that refinement is within bounds } val res = cpy.RefinedTypeTree(tree)(tpt1, refinements1) withType -- cgit v1.2.3 From 9c63c8b56f65d78c4da2b006ac74a260ae748b26 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 5 Jun 2015 15:26:18 +0200 Subject: Make SkolemTypes cached but generative. Skolem[T] != Skolem[T] --- src/dotty/tools/dotc/core/Types.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 109f39c2f..4e1da7c34 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -2335,7 +2335,8 @@ object Types { override def underlying(implicit ctx: Context) = info def derivedSkolemType(info: Type)(implicit ctx: Context) = if (info eq this.info) this else SkolemType(info) - override def computeHash = doHash(info) + override def computeHash: Int = identityHash + override def equals(that: Any) = this eq that.asInstanceOf[AnyRef] override def toString = s"Skolem($info)" } -- cgit v1.2.3 From d5ba5fb89f41432c710e253d084497658be077f8 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 5 Jun 2015 20:23:19 +0200 Subject: Track unstability in asSeenFrom --- src/dotty/tools/dotc/core/TypeOps.scala | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeOps.scala b/src/dotty/tools/dotc/core/TypeOps.scala index a01f8af9f..c4673d569 100644 --- a/src/dotty/tools/dotc/core/TypeOps.scala +++ b/src/dotty/tools/dotc/core/TypeOps.scala @@ -12,19 +12,25 @@ import ast.tpd._ trait TypeOps { this: Context => // TODO: Make standalone object. - final def asSeenFrom(tp: Type, pre: Type, cls: Symbol): Type = + final def asSeenFrom(tp: Type, pre: Type, cls: Symbol): Type = { + val m = if (pre.isStable || ctx.isAfterTyper) null else new AsSeenFromMap(pre, cls) asSeenFrom(tp, pre, cls, null) - + } + final def asSeenFrom(tp: Type, pre: Type, cls: Symbol, theMap: AsSeenFromMap): Type = { 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 if (thiscls.derivesFrom(cls) && pre.baseTypeRef(thiscls).exists) + else if (thiscls.derivesFrom(cls) && pre.baseTypeRef(thiscls).exists) { + if (!pre.isStable && theMap != null && theMap.currentVariance <= 0) { + theMap.unstable = true + } pre match { case SuperType(thispre, _) => thispre case _ => pre } + } else if ((pre.termSymbol is Package) && !(thiscls is Package)) toPrefix(pre.select(nme.PACKAGE), cls, thiscls) else @@ -36,7 +42,16 @@ trait TypeOps { this: Context => // TODO: Make standalone object. case tp: NamedType => val sym = tp.symbol if (sym.isStatic) tp - else tp.derivedSelect(asSeenFrom(tp.prefix, pre, cls, theMap)) + else { + val pre1 = asSeenFrom(tp.prefix, pre, cls, theMap) + if (theMap != null && theMap.unstable) { + pre1.member(tp.name).info match { + case TypeAlias(alias) => return alias + case _ => + } + } + tp.derivedSelect(pre1) + } case tp: ThisType => toPrefix(pre, cls, tp.cls) case _: BoundType | NoPrefix => @@ -57,6 +72,8 @@ trait TypeOps { this: Context => // TODO: Make standalone object. class AsSeenFromMap(pre: Type, cls: Symbol) extends TypeMap { def apply(tp: Type) = asSeenFrom(tp, pre, cls, this) + def currentVariance = variance + var unstable = false } /** Implementation of Types#simplified */ -- cgit v1.2.3 From 84bf5902dba61c88f1b50229bb3afa5a335ded94 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 5 Jun 2015 15:29:15 +0200 Subject: Move deskolemization from TypeComparer to TypeOps It's no longer needed in TypeComparer. We now deskolemize when locally inferring types of vals and defs. --- src/dotty/tools/dotc/core/TypeComparer.scala | 33 +++++----- src/dotty/tools/dotc/core/TypeOps.scala | 97 ++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+), 16 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index 64faa6d93..1f80a8a10 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -16,7 +16,7 @@ import scala.util.control.NonFatal /** Provides methods to compare types. */ -class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling with Skolemization { +class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { implicit val ctx: Context = initctx val state = ctx.typerState @@ -531,19 +531,16 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi * rebase both itself and the member info of `tp` on a freshly created skolem type. */ protected def hasMatchingMember(name: Name, tp1: Type, tp2: RefinedType): Boolean = { - val saved = skolemsState - if (skolemsState == Skolemization.SkolemsDisallowed) skolemsState = Skolemization.SkolemsAllowed - try { - val rebindNeeded = tp2.refinementRefersToThis - val base = if (rebindNeeded) ensureStableSingleton(tp1) else tp1 - val rinfo2 = if (rebindNeeded) tp2.refinedInfo.substRefinedThis(tp2, base) else tp2.refinedInfo - def qualifies(m: SingleDenotation) = isSubType(m.info, rinfo2) - def memberMatches(mbr: Denotation): Boolean = mbr match { // inlined hasAltWith for performance - case mbr: SingleDenotation => qualifies(mbr) - case _ => mbr hasAltWith qualifies - } - /*>|>*/ ctx.traceIndented(i"hasMatchingMember($base . $name :? ${tp2.refinedInfo}) ${base.member(name).info.show} $rinfo2", subtyping) /*<|<*/ { - memberMatches(base member name) || + val rebindNeeded = tp2.refinementRefersToThis + val base = if (rebindNeeded) ensureStableSingleton(tp1) else tp1 + val rinfo2 = if (rebindNeeded) tp2.refinedInfo.substRefinedThis(tp2, base) else tp2.refinedInfo + def qualifies(m: SingleDenotation) = isSubType(m.info, rinfo2) + def memberMatches(mbr: Denotation): Boolean = mbr match { // inlined hasAltWith for performance + case mbr: SingleDenotation => qualifies(mbr) + case _ => mbr hasAltWith qualifies + } + /*>|>*/ ctx.traceIndented(i"hasMatchingMember($base . $name :? ${tp2.refinedInfo}) ${base.member(name).info.show} $rinfo2", subtyping) /*<|<*/ { + memberMatches(base member name) || tp1.isInstanceOf[SingletonType] && { // special case for situations like: // class C { type T } @@ -554,9 +551,13 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi case _ => false } } - } } - finally skolemsState = saved + } + + final def ensureStableSingleton(tp: Type): SingletonType = tp.stripTypeVar match { + case tp: SingletonType if tp.isStable => tp + case tp: ValueType => SkolemType(tp) + case tp: TypeProxy => ensureStableSingleton(tp.underlying) } /** Skip refinements in `tp2` which match corresponding refinements in `tp1`. diff --git a/src/dotty/tools/dotc/core/TypeOps.scala b/src/dotty/tools/dotc/core/TypeOps.scala index c4673d569..fb641f247 100644 --- a/src/dotty/tools/dotc/core/TypeOps.scala +++ b/src/dotty/tools/dotc/core/TypeOps.scala @@ -76,6 +76,103 @@ trait TypeOps { this: Context => // TODO: Make standalone object. var unstable = false } + /** Approximate a type `tp` with a type that does not contain skolem types. + */ + final def deskolemize(tp: Type): Type = deskolemize(tp, 1, Set()) + + private def deskolemize(tp: Type, variance: Int, seen: Set[SkolemType]): Type = { + def approx(lo: Type = defn.NothingType, hi: Type = defn.AnyType, newSeen: Set[SkolemType] = seen) = + if (variance == 0) NoType + else deskolemize(if (variance < 0) lo else hi, variance, newSeen) + tp match { + case tp: SkolemType => + if (seen contains tp) NoType + else approx(hi = tp.info, newSeen = seen + tp) + case tp: NamedType => + val sym = tp.symbol + if (sym.isStatic) tp + else { + val pre1 = deskolemize(tp.prefix, variance, seen) + if (pre1 eq tp.prefix) tp + else { + val d = tp.prefix.member(tp.name) + d.info match { + case TypeAlias(alias) => deskolemize(alias, variance, seen) + case _ => + if (pre1.exists && !pre1.isRef(defn.NothingClass)) tp.derivedSelect(pre1) + else { + ctx.log(s"deskolem: $tp: ${tp.info}") + tp.info match { + case TypeBounds(lo, hi) => approx(lo, hi) + case info => approx(defn.NothingType, info) + } + } + } + } + } + case _: ThisType | _: BoundType | _: SuperType | NoType | NoPrefix => + tp + case tp: RefinedType => + val parent1 = deskolemize(tp.parent, variance, seen) + if (parent1.exists) { + val refinedInfo1 = deskolemize(tp.refinedInfo, variance, seen) + if (refinedInfo1.exists) + tp.derivedRefinedType(parent1, tp.refinedName, refinedInfo1) + else + approx(hi = parent1) + } + else approx() + case tp: TypeAlias => + val alias1 = deskolemize(tp.alias, variance * tp.variance, seen) + if (alias1.exists) tp.derivedTypeAlias(alias1) + else approx(hi = TypeBounds.empty) + case tp: TypeBounds => + val lo1 = deskolemize(tp.lo, -variance, seen) + val hi1 = deskolemize(tp.hi, variance, seen) + if (lo1.exists && hi1.exists) tp.derivedTypeBounds(lo1, hi1) + else approx(hi = + if (lo1.exists) TypeBounds.lower(lo1) + else if (hi1.exists) TypeBounds.upper(hi1) + else TypeBounds.empty) + case tp: ClassInfo => + val pre1 = deskolemize(tp.prefix, variance, seen) + if (pre1.exists) tp.derivedClassInfo(pre1) + else NoType + case tp: AndOrType => + val tp1d = deskolemize(tp.tp1, variance, seen) + val tp2d = deskolemize(tp.tp2, variance, seen) + if (tp1d.exists && tp2d.exists) + tp.derivedAndOrType(tp1d, tp2d) + else if (tp.isAnd) + approx(hi = tp1d & tp2d) // if one of tp1d, tp2d exists, it is the result of tp1d & tp2d + else + approx(lo = tp1d & tp2d) + case tp: WildcardType => + val bounds1 = deskolemize(tp.optBounds, variance, seen) + if (bounds1.exists) tp.derivedWildcardType(bounds1) + else WildcardType + case _ => + if (tp.isInstanceOf[MethodicType]) assert(variance != 0, tp) + deskolemizeMap.mapOver(tp, variance, seen) + } + } + + object deskolemizeMap extends TypeMap { + private var seen: Set[SkolemType] = _ + def apply(tp: Type) = deskolemize(tp, variance, seen) + def mapOver(tp: Type, variance: Int, seen: Set[SkolemType]) = { + val savedVariance = this.variance + val savedSeen = this.seen + this.variance = variance + this.seen = seen + try super.mapOver(tp) + finally { + this.variance = savedVariance + this.seen = savedSeen + } + } + } + /** Implementation of Types#simplified */ final def simplify(tp: Type, theMap: SimplifyMap): Type = tp match { case tp: NamedType => -- cgit v1.2.3 From 0ee8e506dac87bae6ec432b2cd277109df872145 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 5 Jun 2015 22:38:01 +0200 Subject: Skolemize unstable prefixes in asSeenFrom Skolemize unstable prefixes in asSeenFrom provided - the prefix appears at least once in non-variant or contra-variant position - we are in phase typer. After typer, we have already established soundness, so there's no need to do skolemization again. We can simply do the (otherwise unsound) substitution from this-type to prefix. --- src/dotty/tools/dotc/core/Phases.scala | 3 +++ src/dotty/tools/dotc/core/TypeComparer.scala | 5 +++++ src/dotty/tools/dotc/core/TypeOps.scala | 19 +++++++++++-------- src/dotty/tools/dotc/typer/FrontEnd.scala | 1 + test/dotc/tests.scala | 3 +-- tests/neg/i0583-skolemize.scala | 27 +++++++++++++++++++++++++++ 6 files changed, 48 insertions(+), 10 deletions(-) create mode 100644 tests/neg/i0583-skolemize.scala diff --git a/src/dotty/tools/dotc/core/Phases.scala b/src/dotty/tools/dotc/core/Phases.scala index 0ec3320bb..b086308a2 100644 --- a/src/dotty/tools/dotc/core/Phases.scala +++ b/src/dotty/tools/dotc/core/Phases.scala @@ -285,6 +285,9 @@ object Phases { */ def relaxedTyping: Boolean = false + /** Overridden by FrontEnd */ + def isTyper = false + def exists: Boolean = true private var myPeriod: Period = Periods.InvalidPeriod diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index 1f80a8a10..ea815f6c0 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -274,6 +274,11 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { case _ => thirdTry(tp1, tp2) } + case tp1: SkolemType => + tp2 match { + case tp2: SkolemType if !ctx.phase.isTyper && tp1.info <:< tp2.info => true + case _ => thirdTry(tp1, tp2) + } case tp1: TypeVar => isSubType(tp1.underlying, tp2) case tp1: WildcardType => diff --git a/src/dotty/tools/dotc/core/TypeOps.scala b/src/dotty/tools/dotc/core/TypeOps.scala index fb641f247..7c371a96e 100644 --- a/src/dotty/tools/dotc/core/TypeOps.scala +++ b/src/dotty/tools/dotc/core/TypeOps.scala @@ -13,19 +13,19 @@ import ast.tpd._ trait TypeOps { this: Context => // TODO: Make standalone object. final def asSeenFrom(tp: Type, pre: Type, cls: Symbol): Type = { - val m = if (pre.isStable || ctx.isAfterTyper) null else new AsSeenFromMap(pre, cls) - asSeenFrom(tp, pre, cls, null) + val m = if (pre.isStable || !ctx.phase.isTyper) null else new AsSeenFromMap(pre, cls) + var res = asSeenFrom(tp, pre, cls, m) + if (m != null && m.unstable) asSeenFrom(tp, SkolemType(pre), cls) else res } - + final def asSeenFrom(tp: Type, pre: Type, cls: Symbol, theMap: AsSeenFromMap): Type = { 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 if (thiscls.derivesFrom(cls) && pre.baseTypeRef(thiscls).exists) { - if (!pre.isStable && theMap != null && theMap.currentVariance <= 0) { + if (theMap != null && theMap.currentVariance <= 0 && !pre.isStable) theMap.unstable = true - } pre match { case SuperType(thispre, _) => thispre case _ => pre @@ -43,10 +43,13 @@ trait TypeOps { this: Context => // TODO: Make standalone object. val sym = tp.symbol if (sym.isStatic) tp else { + val prevStable = theMap == null || !theMap.unstable val pre1 = asSeenFrom(tp.prefix, pre, cls, theMap) - if (theMap != null && theMap.unstable) { + if (theMap != null && theMap.unstable && prevStable) { pre1.member(tp.name).info match { - case TypeAlias(alias) => return alias + case TypeAlias(alias) => + theMap.unstable = false + return alias case _ => } } @@ -61,7 +64,7 @@ trait TypeOps { this: Context => // TODO: Make standalone object. asSeenFrom(tp.parent, pre, cls, theMap), tp.refinedName, asSeenFrom(tp.refinedInfo, pre, cls, theMap)) - case tp: TypeAlias => + case tp: TypeAlias if theMap == null => // if theMap exists, need to do the variance calculation tp.derivedTypeAlias(asSeenFrom(tp.alias, pre, cls, theMap)) case _ => (if (theMap != null) theMap else new AsSeenFromMap(pre, cls)) diff --git a/src/dotty/tools/dotc/typer/FrontEnd.scala b/src/dotty/tools/dotc/typer/FrontEnd.scala index 056a57215..3dda108b0 100644 --- a/src/dotty/tools/dotc/typer/FrontEnd.scala +++ b/src/dotty/tools/dotc/typer/FrontEnd.scala @@ -13,6 +13,7 @@ import scala.util.control.NonFatal class FrontEnd extends Phase { override def phaseName = "frontend" + override def isTyper = true def monitor(doing: String)(body: => Unit)(implicit ctx: Context) = try body diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index b017211a9..de7f48a68 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -133,15 +133,14 @@ class tests extends CompilerTest { @Test def neg_i0091_infpaths = compileFile(negDir, "i0091-infpaths", xerrors = 3) @Test def neg_i0248_inherit_refined = compileFile(negDir, "i0248-inherit-refined", xerrors = 4) @Test def neg_i0281 = compileFile(negDir, "i0281-null-primitive-conforms", xerrors = 3) + @Test def neg_i583 = compileFile(negDir, "i0583-skolemize", xerrors = 2) @Test def neg_moduleSubtyping = compileFile(negDir, "moduleSubtyping", xerrors = 4) @Test def neg_escapingRefs = compileFile(negDir, "escapingRefs", xerrors = 2) @Test def neg_instantiateAbstract = compileFile(negDir, "instantiateAbstract", xerrors = 8) @Test def neg_selfInheritance = compileFile(negDir, "selfInheritance", xerrors = 5) - @Test def run_all = runFiles(runDir) - @Test def dotty = compileDir(dottyDir, "tools", "-deep" :: allowDeepSubtypes ++ twice) // note the -deep argument diff --git a/tests/neg/i0583-skolemize.scala b/tests/neg/i0583-skolemize.scala new file mode 100644 index 000000000..e0bb99e5d --- /dev/null +++ b/tests/neg/i0583-skolemize.scala @@ -0,0 +1,27 @@ +import scala.collection.mutable.ListBuffer + +object Test1 { + + class Box[T](x: T) + def box[T](x: T) = new Box[T](x) + + class C[T] { def x: T = ???; def x_=(y: T): Unit = ??? } + + val c: C[Int] = ??? + val d: C[Float] = ??? + + val xs: List[C[_]] = List(c, d) + + xs(0).x = xs(1).x + +} +object Test { + def main(args: Array[String]): Unit = { + val f: ListBuffer[Int] = ListBuffer(1,2) + val g: ListBuffer[Double] = ListBuffer(3.0,4.0) + val lb: ListBuffer[ListBuffer[_]] = ListBuffer(f, g) + lb(0)(0) = lb(1)(0) + val x: Int = f(0) + } +} + -- cgit v1.2.3 From 91dd827a726a12395d7b84c03d45ca6822bf8f12 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 5 Jun 2015 17:18:11 +0200 Subject: Deskolemize types inferred for vals and defs We want to establish the invariant (optionally checked by assertNoSkolems) that symbols do not contain skolemized types as their info. This avoids unsoundness situations where a skolem gets exported as part if the result type of a method, so different instantiations look like their are the same instance. --- src/dotty/tools/dotc/config/Config.scala | 5 +++++ src/dotty/tools/dotc/core/SymDenotations.scala | 26 ++++++++++++++++++++++++-- src/dotty/tools/dotc/core/Types.scala | 2 +- src/dotty/tools/dotc/typer/Namer.scala | 3 ++- tests/pos/i583a.scala | 20 ++++++++++++++++++++ 5 files changed, 52 insertions(+), 4 deletions(-) create mode 100644 tests/pos/i583a.scala diff --git a/src/dotty/tools/dotc/config/Config.scala b/src/dotty/tools/dotc/config/Config.scala index 27d5effa5..782a2f2d3 100644 --- a/src/dotty/tools/dotc/config/Config.scala +++ b/src/dotty/tools/dotc/config/Config.scala @@ -32,6 +32,11 @@ object Config { */ final val checkConstraintsPropagated = false + /** Check that no type appearing as the info of a SymDenotation contains + * skolem types. + */ + final val checkNoSkolemsInInfo = false + /** Type comparer will fail with an assert if the upper bound * of a constrained parameter becomes Nothing. This should be turned * on only for specific debugging as normally instantiation to Nothing diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index f24c41ee2..8162126a5 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -79,6 +79,7 @@ object SymDenotations { super.validFor_=(p) } */ + if (Config.checkNoSkolemsInInfo) assertNoSkolems(initInfo) // ------ Getting and setting fields ----------------------------- @@ -168,8 +169,8 @@ object SymDenotations { } protected[dotc] final def info_=(tp: Type) = { - /* - def illegal: String = s"illegal type for $this: $tp" + /* // DEBUG + def illegal: String = s"illegal type for $this: $tp" if (this is Module) // make sure module invariants that allow moduleClass and sourceModule to work are kept. tp match { case tp: ClassInfo => assert(tp.selfInfo.isInstanceOf[TermRefBySym], illegal) @@ -178,6 +179,7 @@ object SymDenotations { case _ => } */ + if (Config.checkNoSkolemsInInfo) assertNoSkolems(initInfo) myInfo = tp } @@ -1038,8 +1040,28 @@ object SymDenotations { s"$kindString $name" } + // ----- Sanity checks and debugging */ + def debugString = toString + "#" + symbol.id // !!! DEBUG + def hasSkolems(tp: Type): Boolean = tp match { + case tp: SkolemType => true + case tp: NamedType => hasSkolems(tp.prefix) + case tp: RefinedType => hasSkolems(tp.parent) || hasSkolems(tp.refinedInfo) + case tp: PolyType => tp.paramBounds.exists(hasSkolems) || hasSkolems(tp.resType) + case tp: MethodType => tp.paramTypes.exists(hasSkolems) || hasSkolems(tp.resType) + case tp: ExprType => hasSkolems(tp.resType) + case tp: AndOrType => hasSkolems(tp.tp1) || hasSkolems(tp.tp2) + case tp: TypeBounds => hasSkolems(tp.lo) || hasSkolems(tp.hi) + case tp: AnnotatedType => hasSkolems(tp.tpe) + case tp: TypeVar => hasSkolems(tp.inst) + case _ => false + } + + def assertNoSkolems(tp: Type) = + if (!this.isSkolem) + assert(!hasSkolems(tp), s"assigning type $tp containing skolems to $this") + // ----- copies and transforms ---------------------------------------- protected def newLikeThis(s: Symbol, i: Type): SingleDenotation = new UniqueRefDenotation(s, i, validFor) diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 4e1da7c34..ba39e7bef 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -2011,7 +2011,7 @@ object Types { def isJava = false def isImplicit = false - private val resType = resultTypeExp(this) + private[core] val resType = resultTypeExp(this) assert(resType.exists) override def resultType(implicit ctx: Context): Type = diff --git a/src/dotty/tools/dotc/typer/Namer.scala b/src/dotty/tools/dotc/typer/Namer.scala index 10667f884..2e76d3171 100644 --- a/src/dotty/tools/dotc/typer/Namer.scala +++ b/src/dotty/tools/dotc/typer/Namer.scala @@ -677,7 +677,8 @@ class Namer { typer: Typer => // println(s"final inherited for $sym: ${inherited.toString}") !!! // println(s"owner = ${sym.owner}, decls = ${sym.owner.info.decls.show}") val rhsCtx = ctx.fresh addMode Mode.InferringReturnType - def rhsType = typedAheadExpr(mdef.rhs, rhsProto)(rhsCtx).tpe.widen.approximateUnion + def rhsType = ctx.deskolemize( + typedAheadExpr(mdef.rhs, rhsProto)(rhsCtx).tpe.widen.approximateUnion) def lhsType = fullyDefinedType(rhsType, "right-hand side", mdef.pos) if (inherited.exists) inherited else { diff --git a/tests/pos/i583a.scala b/tests/pos/i583a.scala new file mode 100644 index 000000000..a97a3998b --- /dev/null +++ b/tests/pos/i583a.scala @@ -0,0 +1,20 @@ +object Test1 { + + class Box[B](x: B) + + class C { + type T + val box: Box[T] = ??? + def f(x: T): T = ??? + def x: T = ??? + } + + def c: C = new C + + val b = c.box + + val f = c.f _ + +} + + -- cgit v1.2.3 From 540aa23583b12483580c8fbb05f1f392758cd8df Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 5 Jun 2015 22:39:33 +0200 Subject: Remove no longer needed Skolemization.scala --- src/dotty/tools/dotc/core/Skolemization.scala | 145 -------------------------- 1 file changed, 145 deletions(-) delete mode 100644 src/dotty/tools/dotc/core/Skolemization.scala diff --git a/src/dotty/tools/dotc/core/Skolemization.scala b/src/dotty/tools/dotc/core/Skolemization.scala deleted file mode 100644 index 8baf612ba..000000000 --- a/src/dotty/tools/dotc/core/Skolemization.scala +++ /dev/null @@ -1,145 +0,0 @@ -package dotty.tools.dotc -package core - -import Symbols._, Types._, Contexts._, Decorators._ -import collection.mutable - -/** Methods to add and remove skolemtypes. - * - * Skolem types are generated when comparing refinements. - * A skolem type is simply a fresh singleton type that has a given type - * as underlying type. - * Two skolem types are equal if they refer to the same underlying type. - * To avoid unsoundness, skolem types have to be kept strictly local to the - * comparison, they are not allowed to escape the lifetime of a comparison - * by surviving in a context or in GADT bounds. - */ -trait Skolemization { - - implicit val ctx: Context - - private[core] var skolemsState = Skolemization.SkolemsDisallowed - - final def ensureStableSingleton(tp: Type): SingletonType = tp.stripTypeVar match { - case tp: SingletonType if tp.isStable => - tp - case tp: ValueType => - assert(skolemsState != Skolemization.SkolemsDisallowed) - skolemsState = Skolemization.SkolemsEncountered - SkolemType(tp) - case tp: TypeProxy => - ensureStableSingleton(tp.underlying) - } -/*@@@ - /** If skolems were encountered, approximate a type `tp` with a type that - * does not contain skolem types. - * @param toSuper if true, return the smallest supertype of `tp` with this property - * else return the largest subtype. - */ - final def deSkolemizeIfSkolemsSeen(tp: Type, toSuper: Boolean): Type = - if (skolemsState == Skolemization.SkolemsEncountered) - deSkolemize(tp, if (toSuper) 1 else -1, Set()) - else tp - - /** Approximate a type `tp` with a type that does not contain skolem types. - */ - final def deSkolemize(tp: Type): Type = deSkolemize(tp, 1, Set()) - - private def deSkolemize(tp: Type, variance: Int, seen: Set[SkolemType]): Type = - ctx.traceIndented(i"deskolemize $tp, variance = $variance, seen = $seen = ", show = true) { - def approx(lo: Type = defn.NothingType, hi: Type = defn.AnyType, newSeen: Set[SkolemType] = seen) = - if (variance == 0) NoType - else deSkolemize(if (variance < 0) lo else hi, variance, newSeen) - tp match { - case tp: SkolemType => - if (seen contains tp) NoType - else approx(hi = tp.binder, newSeen = seen + tp) - case tp: NamedType => - val sym = tp.symbol - if (sym.isStatic) tp - else { - val pre1 = deSkolemize(tp.prefix, variance, seen) - if (pre1 eq tp.prefix) tp - else { - val d = tp.prefix.member(tp.name) - d.info match { - case TypeAlias(alias) => deSkolemize(alias, variance, seen) - case _ => - if (pre1.exists && !pre1.isRef(defn.NothingClass)) tp.derivedSelect(pre1) - else { - ctx.log(s"deskolem: $tp: ${tp.info}") - tp.info match { - case TypeBounds(lo, hi) => approx(lo, hi) - case info => approx(defn.NothingType, info) - } - } - } - } - } - case _: ThisType | _: BoundType | _: SuperType | NoType | NoPrefix => - tp - case tp: RefinedType => - val parent1 = deSkolemize(tp.parent, variance, seen) - if (parent1.exists) { - val refinedInfo1 = deSkolemize(tp.refinedInfo, variance, seen) - if (refinedInfo1.exists) - tp.derivedRefinedType(parent1, tp.refinedName, refinedInfo1) - else - approx(hi = parent1) - } - else approx() - case tp: TypeAlias => - val alias1 = deSkolemize(tp.alias, variance * tp.variance, seen) - if (alias1.exists) tp.derivedTypeAlias(alias1) - else approx(hi = TypeBounds.empty) - case tp: TypeBounds => - val lo1 = deSkolemize(tp.lo, -variance, seen) - val hi1 = deSkolemize(tp.hi, variance, seen) - if (lo1.exists && hi1.exists) tp.derivedTypeBounds(lo1, hi1) - else approx(hi = - if (lo1.exists) TypeBounds.lower(lo1) - else if (hi1.exists) TypeBounds.upper(hi1) - else TypeBounds.empty) - case tp: ClassInfo => - val pre1 = deSkolemize(tp.prefix, variance, seen) - if (pre1.exists) tp.derivedClassInfo(pre1) - else NoType - case tp: AndOrType => - val tp1d = deSkolemize(tp.tp1, variance, seen) - val tp2d = deSkolemize(tp.tp2, variance, seen) - if (tp1d.exists && tp2d.exists) - tp.derivedAndOrType(tp1d, tp2d) - else if (tp.isAnd) - approx(hi = tp1d & tp2d) // if one of tp1d, tp2d exists, it is the result of tp1d & tp2d - else - approx(lo = tp1d & tp2d) - case tp: WildcardType => - val bounds1 = deSkolemize(tp.optBounds, variance, seen) - if (bounds1.exists) tp.derivedWildcardType(bounds1) - else WildcardType - case _ => - if (tp.isInstanceOf[MethodicType]) assert(variance != 0, tp) - deSkolemizeMap.mapOver(tp, variance, seen) - } - } - - object deSkolemizeMap extends TypeMap { - private var seen: Set[SkolemType] = _ - def apply(tp: Type) = deSkolemize(tp, variance, seen) - def mapOver(tp: Type, variance: Int, seen: Set[SkolemType]) = { - val savedVariance = this.variance - val savedSeen = this.seen - this.variance = variance - this.seen = seen - try super.mapOver(tp) - finally { - this.variance = savedVariance - this.seen = savedSeen - } - } - }*/ -} - -object Skolemization extends Enumeration { - val SkolemsDisallowed, SkolemsAllowed, SkolemsEncountered = Value -} -- cgit v1.2.3 From 63aaa797a00f8a0361bb74d105c9f856a233fdd1 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 5 Jun 2015 23:09:29 +0200 Subject: Delete test which no longer applies neg/projections required certain types of the form C#T to be ill-formed. This is no longer done. --- test/dotc/tests.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index de7f48a68..a7123602b 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -104,7 +104,6 @@ class tests extends CompilerTest { @Test def neg_companions = compileFile(negDir, "companions", xerrors = 1) @Test def neg_over = compileFile(negDir, "over", xerrors = 3) @Test def neg_overrides = compileFile(negDir, "overrides", xerrors = 11) - @Test def neg_projections = compileFile(negDir, "projections", xerrors = 1) @Test def neg_i39 = compileFile(negDir, "i39", xerrors = 2) @Test def neg_i50_volatile = compileFile(negDir, "i50-volatile", xerrors = 6) @Test def neg_t0273_doubledefs = compileFile(negDir, "t0273", xerrors = 1) -- cgit v1.2.3 From 081b6ad358467b620f584779737f1fdc8c8300d0 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 6 Jun 2015 10:53:05 +0200 Subject: Document asSeenFrom --- src/dotty/tools/dotc/core/TypeOps.scala | 48 +++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeOps.scala b/src/dotty/tools/dotc/core/TypeOps.scala index 7c371a96e..2b2ef83a2 100644 --- a/src/dotty/tools/dotc/core/TypeOps.scala +++ b/src/dotty/tools/dotc/core/TypeOps.scala @@ -12,14 +12,50 @@ 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 it's 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. We must skolemize if an unstable prefix is used in nonvariant or + * contravariant position of the return type of asSeenFrom. + * + * In the implementation of asSeenFrom, we first try to run asSeenFrom without + * skolemizing. If that would be incorrect we will be told by the fact that + * `unstable` is set in the passed AsSeenFromMap. In that case we run asSeenFrom + * again with a skolemized prefix. + * + * In the interest of speed we want to avoid creating an AsSeenFromMap every time + * asSeenFrom is called. So we do this here only if the prefix is unstable + * (because then we need the map as a container for the unstable field). For + * stable prefixes the map is `null`; it might however be instantiated later + * for more complicated types. + */ final def asSeenFrom(tp: Type, pre: Type, cls: Symbol): Type = { val m = if (pre.isStable || !ctx.phase.isTyper) null else new AsSeenFromMap(pre, cls) var res = asSeenFrom(tp, pre, cls, m) if (m != null && m.unstable) asSeenFrom(tp, SkolemType(pre), cls) else res } - final def asSeenFrom(tp: Type, pre: Type, cls: Symbol, theMap: AsSeenFromMap): Type = { + /** 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 @@ -48,6 +84,7 @@ trait TypeOps { this: Context => // TODO: Make standalone object. if (theMap != null && theMap.unstable && prevStable) { pre1.member(tp.name).info match { case TypeAlias(alias) => + // try to follow aliases of this will avoid skolemization. theMap.unstable = false return alias case _ => @@ -73,13 +110,20 @@ trait TypeOps { this: Context => // TODO: Make standalone object. } } + /** 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 + + /** A field which indicates whether an unstable argument in nonvariant + * or contravariant position was encountered. + */ var unstable = false } - /** Approximate a type `tp` with a type that does not contain skolem types. + /** Approximate a type `tp` with a type that does not contain skolem types. */ final def deskolemize(tp: Type): Type = deskolemize(tp, 1, Set()) -- cgit v1.2.3 From 310615e717262243aa899959c3178d7465af74a7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 6 Jun 2015 17:44:08 +0200 Subject: Better printing of skolem types --- src/dotty/tools/dotc/printing/PlainPrinter.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/printing/PlainPrinter.scala b/src/dotty/tools/dotc/printing/PlainPrinter.scala index de1a439cf..a7b338be8 100644 --- a/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -223,7 +223,7 @@ class PlainPrinter(_ctx: Context) extends Printer { case SuperType(thistpe: SingletonType, _) => toTextRef(thistpe).map(_.replaceAll("""\bthis$""", "super")) case SuperType(thistpe, _) => - "Super(" ~ toTextLocal(thistpe) ~ ")" + "Super(" ~ toTextGlobal(thistpe) ~ ")" case tp @ ConstantType(value) => toText(value) case MethodParam(mt, idx) => @@ -231,7 +231,7 @@ class PlainPrinter(_ctx: Context) extends Printer { case tp: RefinedThis => s"${nameString(tp.binder.typeSymbol)}{...}.this" case tp: SkolemType => - "" // !!! todo refine with unique identifier. + "" } } -- cgit v1.2.3