diff options
12 files changed, 326 insertions, 224 deletions
diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index fea9e72512..4e5f4faf51 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -263,6 +263,8 @@ class Global(var currentSettings: Settings, var reporter: Reporter) @inline final override def devWarning(msg: => String) { if (settings.developer.value || settings.debug.value) warning("!!! " + msg) + else + log("!!! " + msg) // such warnings always at least logged } private def elapsedMessage(msg: String, start: Long) = diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala index a5496f829d..2c9c20666d 100644 --- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala @@ -167,7 +167,8 @@ trait ScalaSettings extends AbsScalaSettings val Yreploutdir = StringSetting ("-Yrepl-outdir", "path", "Write repl-generated classfiles to given output directory (use \"\" to generate a temporary dir)" , "") val Ynotnull = BooleanSetting ("-Ynotnull", "Enable (experimental and incomplete) scala.NotNull.") val YmethodInfer = BooleanSetting ("-Yinfer-argument-types", "Infer types for arguments of overriden methods.") - val etaExpandKeepsStar = BooleanSetting ("-Yeta-expand-keeps-star", "Eta-expand varargs methods to T* rather than Seq[T]. This is a temporary option to ease transition.") + val etaExpandKeepsStar = BooleanSetting ("-Yeta-expand-keeps-star", "Eta-expand varargs methods to T* rather than Seq[T]. This is a temporary option to ease transition."). + withDeprecationMessage("This flag is scheduled for removal in 2.12. If you have a case where you need this flag then please report a bug.") val Yinvalidate = StringSetting ("-Yinvalidate", "classpath-entry", "Invalidate classpath entry before run", "") val noSelfCheck = BooleanSetting ("-Yno-self-type-checks", "Suppress check for self-type conformance among inherited members.") val YvirtClasses = false // too embryonic to even expose as a -Y //BooleanSetting ("-Yvirtual-classes", "Support virtual classes") diff --git a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala index eb91251930..26e39d3d1b 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala @@ -8,6 +8,7 @@ package typechecker import scala.collection.mutable import scala.annotation.tailrec +import scala.reflect.internal.util.shortClassOfInstance /** * @author Martin Odersky @@ -175,6 +176,7 @@ trait Contexts { self: Analyzer => if ((owner eq NoSymbol) || (owner.isClass) || (owner.isMethod)) this else outer.enclClassOrMethod + def enclosingCaseDef = nextEnclosing(_.tree.isInstanceOf[CaseDef]) def undetparamsString = if (undetparams.isEmpty) "" else undetparams.mkString("undetparams=", ", ", "") @@ -584,23 +586,39 @@ trait Contexts { self: Analyzer => } def pushTypeBounds(sym: Symbol) { + sym.info match { + case tb: TypeBounds => if (!tb.isEmptyBounds) log(s"Saving $sym info=$tb") + case info => devWarning(s"Something other than a TypeBounds seen in pushTypeBounds: $info is a ${shortClassOfInstance(info)}") + } savedTypeBounds ::= ((sym, sym.info)) } def restoreTypeBounds(tp: Type): Type = { - var current = tp - for ((sym, info) <- savedTypeBounds) { - debuglog("resetting " + sym + " to " + info) - sym.info match { - case TypeBounds(lo, hi) if (hi <:< lo && lo <:< hi) => - current = current.instantiateTypeParams(List(sym), List(lo)) -//@M TODO: when higher-kinded types are inferred, probably need a case PolyType(_, TypeBounds(...)) if ... => - case _ => - } - sym.setInfo(info) + def restore(): Type = savedTypeBounds.foldLeft(tp) { case (current, (sym, savedInfo)) => + def bounds_s(tb: TypeBounds) = if (tb.isEmptyBounds) "<empty bounds>" else s"TypeBounds(lo=${tb.lo}, hi=${tb.hi})" + //@M TODO: when higher-kinded types are inferred, probably need a case PolyType(_, TypeBounds(...)) if ... => + val tb @ TypeBounds(lo, hi) = sym.info.bounds + val isUnique = lo <:< hi && hi <:< lo + val isPresent = current contains sym + def saved_s = bounds_s(savedInfo.bounds) + def current_s = bounds_s(sym.info.bounds) + + if (isUnique && isPresent) + devWarningResult(s"Preserving inference: ${sym.nameString}=$hi in $current (based on $current_s) before restoring $sym to saved $saved_s")( + current.instantiateTypeParams(List(sym), List(hi)) + ) + else if (isPresent) + devWarningResult(s"Discarding inferred $current_s because it does not uniquely determine $sym in")(current) + else + logResult(s"Discarding inferred $current_s because $sym does not appear in")(current) + } + try restore() + finally { + for ((sym, savedInfo) <- savedTypeBounds) + sym setInfo debuglogResult(s"Discarding inferred $sym=${sym.info}, restoring saved info")(savedInfo) + + savedTypeBounds = Nil } - savedTypeBounds = List() - current } private var implicitsCache: List[List[ImplicitInfo]] = null diff --git a/src/compiler/scala/tools/nsc/typechecker/Infer.scala b/src/compiler/scala/tools/nsc/typechecker/Infer.scala index db3759d65f..a29cc93b6d 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Infer.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Infer.scala @@ -1317,15 +1317,18 @@ trait Infer extends Checkable { } } - def instBounds(tvar: TypeVar): (Type, Type) = { - val tparam = tvar.origin.typeSymbol - val instType = toOrigin(tvar.constr.inst) + def instBounds(tvar: TypeVar): TypeBounds = { + val tparam = tvar.origin.typeSymbol + val instType = toOrigin(tvar.constr.inst) + val TypeBounds(lo, hi) = tparam.info.bounds val (loBounds, hiBounds) = - if (instType != NoType && isFullyDefined(instType)) (List(instType), List(instType)) + if (isFullyDefined(instType)) (List(instType), List(instType)) else (tvar.constr.loBounds, tvar.constr.hiBounds) - val lo = lub(tparam.info.bounds.lo :: loBounds map toOrigin) - val hi = glb(tparam.info.bounds.hi :: hiBounds map toOrigin) - (lo, hi) + + TypeBounds( + lub(lo :: loBounds map toOrigin), + glb(hi :: hiBounds map toOrigin) + ) } def isInstantiatable(tvars: List[TypeVar]) = { @@ -1335,33 +1338,25 @@ trait Infer extends Checkable { solve(tvars1, tvars1 map (_.origin.typeSymbol), tvars1 map (_ => Variance.Covariant), false) } - // this is quite nasty: it destructively changes the info of the syms of e.g., method type params (see #3692, where the type param T's bounds were set to >: T <: T, so that parts looped) + // this is quite nasty: it destructively changes the info of the syms of e.g., method type params + // (see #3692, where the type param T's bounds were set to > : T <: T, so that parts looped) // the changes are rolled back by restoreTypeBounds, but might be unintentially observed in the mean time def instantiateTypeVar(tvar: TypeVar) { - val tparam = tvar.origin.typeSymbol - if (false && - tvar.constr.inst != NoType && - isFullyDefined(tvar.constr.inst) && - (tparam.info.bounds containsType tvar.constr.inst)) { - context.nextEnclosing(_.tree.isInstanceOf[CaseDef]).pushTypeBounds(tparam) - tparam setInfo tvar.constr.inst - tparam resetFlag DEFERRED - debuglog("new alias of " + tparam + " = " + tparam.info) - } else { - val (lo, hi) = instBounds(tvar) - if (lo <:< hi) { - if (!((lo <:< tparam.info.bounds.lo) && (tparam.info.bounds.hi <:< hi)) // bounds were improved - && tparam != lo.typeSymbolDirect && tparam != hi.typeSymbolDirect) { // don't create illegal cycles - context.nextEnclosing(_.tree.isInstanceOf[CaseDef]).pushTypeBounds(tparam) - tparam setInfo TypeBounds(lo, hi) - debuglog("new bounds of " + tparam + " = " + tparam.info) - } else { - debuglog("redundant: "+tparam+" "+tparam.info+"/"+lo+" "+hi) - } - } else { - debuglog("inconsistent: "+tparam+" "+lo+" "+hi) + val tparam = tvar.origin.typeSymbol + val TypeBounds(lo0, hi0) = tparam.info.bounds + val tb @ TypeBounds(lo1, hi1) = instBounds(tvar) + + if (lo1 <:< hi1) { + if (lo1 <:< lo0 && hi0 <:< hi1) // bounds unimproved + log(s"redundant bounds: discarding TypeBounds($lo1, $hi1) for $tparam, no improvement on TypeBounds($lo0, $hi0)") + else if (tparam == lo1.typeSymbolDirect || tparam == hi1.typeSymbolDirect) + log(s"cyclical bounds: discarding TypeBounds($lo1, $hi1) for $tparam because $tparam appears as bounds") + else { + context.enclosingCaseDef pushTypeBounds tparam + tparam setInfo logResult(s"updated bounds: $tparam from ${tparam.info} to")(tb) } } + else log(s"inconsistent bounds: discarding TypeBounds($lo1, $hi1)") } /** Type intersection of simple type tp1 with general type tp2. @@ -1524,7 +1519,7 @@ trait Infer extends Checkable { // todo: missing test case for bests.isEmpty bests match { case best :: Nil => tree setSymbol best setType (pre memberType best) - case best :: competing :: _ if alts0.nonEmpty => + case best :: competing :: _ if alts0.nonEmpty => // SI-6912 Don't give up and leave an OverloadedType on the tree. // Originally I wrote this as `if (secondTry) ... `, but `tryTwice` won't attempt the second try // unless an error is issued. We're not issuing an error, in the assumption that it would be diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index c40b69bc7a..9680b911e0 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -2438,16 +2438,13 @@ trait Typers extends Adaptations with Tags { else typed(cdef.guard, BooleanClass.tpe) var body1: Tree = typed(cdef.body, pt) - val contextWithTypeBounds = context.nextEnclosing(_.tree.isInstanceOf[CaseDef]) - if (contextWithTypeBounds.savedTypeBounds.nonEmpty) { - body1 modifyType (contextWithTypeBounds restoreTypeBounds _) - + if (context.enclosingCaseDef.savedTypeBounds.nonEmpty) { + body1 modifyType context.enclosingCaseDef.restoreTypeBounds // insert a cast if something typechecked under the GADT constraints, // but not in real life (i.e., now that's we've reset the method's type skolems' // infos back to their pre-GADT-constraint state) if (isFullyDefined(pt) && !(body1.tpe <:< pt)) body1 = typedPos(body1.pos)(gen.mkCast(body1, pt.dealiasWiden)) - } // body1 = checkNoEscaping.locals(context.scope, pt, body1) diff --git a/src/library/scala/collection/TraversableOnce.scala b/src/library/scala/collection/TraversableOnce.scala index 679e8e3e61..fcca2da437 100644 --- a/src/library/scala/collection/TraversableOnce.scala +++ b/src/library/scala/collection/TraversableOnce.scala @@ -383,17 +383,17 @@ object TraversableOnce { new FlattenOps[A](travs map ev) /* Functionality reused in Iterator.CanBuildFrom */ - private[collection] abstract class BufferedCanBuildFrom[A, Coll[X] <: TraversableOnce[X]] extends generic.CanBuildFrom[Coll[_], A, Coll[A]] { - def bufferToColl[B](buff: ArrayBuffer[B]): Coll[B] - def traversableToColl[B](t: GenTraversable[B]): Coll[B] + private[collection] abstract class BufferedCanBuildFrom[A, CC[X] <: TraversableOnce[X]] extends generic.CanBuildFrom[CC[_], A, CC[A]] { + def bufferToColl[B](buff: ArrayBuffer[B]): CC[B] + def traversableToColl[B](t: GenTraversable[B]): CC[B] - def newIterator: Builder[A, Coll[A]] = new ArrayBuffer[A] mapResult bufferToColl + def newIterator: Builder[A, CC[A]] = new ArrayBuffer[A] mapResult bufferToColl /** Creates a new builder on request of a collection. * @param from the collection requesting the builder to be created. * @return the result of invoking the `genericBuilder` method on `from`. */ - def apply(from: Coll[_]): Builder[A, Coll[A]] = from match { + def apply(from: CC[_]): Builder[A, CC[A]] = from match { case xs: generic.GenericTraversableTemplate[_, _] => xs.genericBuilder.asInstanceOf[Builder[A, Traversable[A]]] mapResult { case res => traversableToColl(res.asInstanceOf[GenTraversable[A]]) } diff --git a/src/reflect/scala/reflect/internal/SymbolTable.scala b/src/reflect/scala/reflect/internal/SymbolTable.scala index 9b5778b9da..03ec59f0fe 100644 --- a/src/reflect/scala/reflect/internal/SymbolTable.scala +++ b/src/reflect/scala/reflect/internal/SymbolTable.scala @@ -87,6 +87,16 @@ abstract class SymbolTable extends macros.Universe result } @inline + final private[scala] def debuglogResult[T](msg: => String)(result: T): T = { + debuglog(msg + ": " + result) + result + } + @inline + final private[scala] def devWarningResult[T](msg: => String)(result: T): T = { + devWarning(msg + ": " + result) + result + } + @inline final private[scala] def logResultIf[T](msg: => String, cond: T => Boolean)(result: T): T = { if (cond(result)) log(msg + ": " + result) diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index 03419dd576..ff83cb5f26 100644 --- a/src/reflect/scala/reflect/internal/Symbols.scala +++ b/src/reflect/scala/reflect/internal/Symbols.scala @@ -940,6 +940,9 @@ trait Symbols extends api.Symbols { self: SymbolTable => rawowner } + // Like owner, but NoSymbol.owner == NoSymbol instead of throwing an exception. + final def safeOwner: Symbol = if (this eq NoSymbol) NoSymbol else owner + // TODO - don't allow the owner to be changed without checking invariants, at least // when under some flag. Define per-phase invariants for owner/owned relationships, // e.g. after flatten all classes are owned by package classes, there are lots and diff --git a/src/reflect/scala/reflect/internal/Types.scala b/src/reflect/scala/reflect/internal/Types.scala index f7ee4a7e7f..22ba6d43e9 100644 --- a/src/reflect/scala/reflect/internal/Types.scala +++ b/src/reflect/scala/reflect/internal/Types.scala @@ -736,7 +736,7 @@ trait Types extends api.Types { self: SymbolTable => ) if (trivial) this else { - val m = new AsSeenFromMap(pre.normalize, clazz) + val m = newAsSeenFromMap(pre.normalize, clazz) val tp = m(this) val tp1 = existentialAbstraction(m.capturedParams, tp) @@ -2250,7 +2250,7 @@ trait Types extends api.Types { self: SymbolTable => else ErrorType } - // isHKSubType0 introduces synthetic type params so that + // isHKSubType introduces synthetic type params so that // betaReduce can first apply sym.info to typeArgs before calling // asSeenFrom. asSeenFrom then skips synthetic type params, which // are used to reduce HO subtyping to first-order subtyping, but @@ -4348,147 +4348,213 @@ trait Types extends api.Types { self: SymbolTable => (pre eq NoType) || (pre eq NoPrefix) || !isPossiblePrefix(clazz) ) - /** A map to compute the asSeenFrom method */ - class AsSeenFromMap(pre: Type, clazz: Symbol) extends TypeMap with KeepOnlyTypeConstraints { - var capturedSkolems: List[Symbol] = List() - var capturedParams: List[Symbol] = List() + def newAsSeenFromMap(pre: Type, clazz: Symbol): AsSeenFromMap = + new AsSeenFromMap(pre, clazz) - override def mapOver(tree: Tree, giveup: ()=>Nothing): Tree = { - object annotationArgRewriter extends TypeMapTransformer { - private def canRewriteThis(sym: Symbol) = ( - (sym isNonBottomSubClass clazz) - && (pre.widen.typeSymbol isNonBottomSubClass sym) - && (pre.isStable || giveup()) - ) - // what symbol should really be used? - private def newTermSym() = { - val p = pre.typeSymbol - p.owner.newValue(p.name.toTermName, p.pos) setInfo pre - } - /** Rewrite `This` trees in annotation argument trees */ - override def transform(tree: Tree): Tree = super.transform(tree) match { - case This(_) if canRewriteThis(tree.symbol) => gen.mkAttributedQualifier(pre, newTermSym()) - case tree => tree - } - } - annotationArgRewriter.transform(tree) + /** A map to compute the asSeenFrom method. + */ + class AsSeenFromMap(seenFromPrefix: Type, seenFromClass: Symbol) extends TypeMap with KeepOnlyTypeConstraints { + // Some example source constructs relevant in asSeenFrom: + // + // object CaptureThis { + // trait X[A] { def f: this.type = this } + // class Y[A] { def f: this.type = this } + // // Created new existential to represent This(CaptureThis.X) seen from CaptureThis.X[B]: type _1.type <: CaptureThis.X[B] with Singleton + // def f1[B] = new X[B] { } + // // TODO - why is the behavior different when it's a class? + // def f2[B] = new Y[B] { } + // } + // class CaptureVal[T] { + // val f: java.util.List[_ <: T] = null + // // Captured existential skolem for type _$1 seen from CaptureVal.this.f.type: type _$1 + // def g = f get 0 + // } + // class ClassParam[T] { + // // AsSeenFromMap(Inner.this.type, class Inner)/classParameterAsSeen(T)#loop(ClassParam.this.type, class ClassParam) + // class Inner(lhs: T) { def f = lhs } + // } + def capturedParams: List[Symbol] = _capturedParams + def capturedSkolems: List[Symbol] = _capturedSkolems + + def apply(tp: Type): Type = tp match { + case tp @ ThisType(_) => thisTypeAsSeen(tp) + case tp @ SingleType(_, sym) => if (sym.isPackageClass) tp else singleTypeAsSeen(tp) + case tp @ TypeRef(_, sym, _) if isTypeParamOfEnclosingClass(sym) => classParameterAsSeen(tp) + case _ => mapOver(tp) + } + + private var _capturedSkolems: List[Symbol] = Nil + private var _capturedParams: List[Symbol] = Nil + private val isStablePrefix = seenFromPrefix.isStable + + // isBaseClassOfEnclosingClassOrInfoIsNotYetComplete would be a more accurate + // but less succinct name. + private def isBaseClassOfEnclosingClass(base: Symbol) = { + def loop(encl: Symbol): Boolean = ( + isPossiblePrefix(encl) + && ((encl isSubClass base) || loop(encl.owner.enclClass)) + ) + // The hasCompleteInfo guard is necessary to avoid cycles during the typing + // of certain classes, notably ones defined inside package objects. + !base.hasCompleteInfo || loop(seenFromClass) } - def stabilize(pre: Type, clazz: Symbol): Type = { + /** Is the symbol a class type parameter from one of the enclosing + * classes, or a base class of one of them? + */ + private def isTypeParamOfEnclosingClass(sym: Symbol): Boolean = ( + sym.isTypeParameter + && sym.owner.isClass + && isBaseClassOfEnclosingClass(sym.owner) + ) + + /** Creates an existential representing a type parameter which appears + * in the prefix of a ThisType. + */ + protected def captureThis(pre: Type, clazz: Symbol): Type = { capturedParams find (_.owner == clazz) match { - case Some(qvar) => qvar.tpe - case _ => + case Some(p) => p.tpe + case _ => val qvar = clazz freshExistential nme.SINGLETON_SUFFIX setInfo singletonBounds(pre) - capturedParams ::= qvar + _capturedParams ::= qvar + debuglog(s"Captured This(${clazz.fullNameString}) seen from $seenFromPrefix: ${qvar.defString}") qvar.tpe } } + protected def captureSkolems(skolems: List[Symbol]) { + for (p <- skolems; if !(capturedSkolems contains p)) { + debuglog(s"Captured $p seen from $seenFromPrefix") + _capturedSkolems ::= p + } + } - def apply(tp: Type): Type = - tp match { - case ThisType(sym) => - def toPrefix(pre: Type, clazz: Symbol): Type = - if (skipPrefixOf(pre, clazz)) tp - else if ((sym isNonBottomSubClass clazz) && - (pre.widen.typeSymbol isNonBottomSubClass sym)) { - val pre1 = pre match { - case SuperType(thistp, _) => thistp - case _ => pre - } - if (!(pre1.isStable || - pre1.typeSymbol.isPackageClass || - pre1.typeSymbol.isModuleClass && pre1.typeSymbol.isStatic)) { - stabilize(pre1, sym) - } else { - pre1 - } - } else { - toPrefix(pre.baseType(clazz).prefix, clazz.owner) - } - toPrefix(pre, clazz) - case SingleType(pre, sym) => - if (sym.isPackageClass) tp // short path - else { - val pre1 = this(pre) - if (pre1 eq pre) tp - else if (pre1.isStable) singleType(pre1, sym) - else pre1.memberType(sym).resultType //todo: this should be rolled into existential abstraction - } - // AM: Martin, is this description accurate? - // walk the owner chain of `clazz` (the original argument to asSeenFrom) until we find the type param's owner (while rewriting pre as we crawl up the owner chain) - // once we're at the owner, extract the information that pre encodes about the type param, - // by minimally subsuming pre to the type instance of the class that owns the type param, - // the type we're looking for is the type instance's type argument at the position corresponding to the type parameter - // optimisation: skip this type parameter if it's not owned by a class, as those params are not influenced by the prefix through which they are seen - // (concretely: type params of anonymous type functions, which currently can only arise from normalising type aliases, are owned by the type alias of which they are the eta-expansion) - // (skolems also aren't affected: they are ruled out by the isTypeParameter check) - case TypeRef(prefix, sym, args) if (sym.isTypeParameter && sym.owner.isClass) => - def toInstance(pre: Type, clazz: Symbol): Type = - if (skipPrefixOf(pre, clazz)) mapOver(tp) - //@M! see test pos/tcpoly_return_overriding.scala why mapOver is necessary - else { - def throwError = abort("" + tp + sym.locationString + " cannot be instantiated from " + pre.widen) - - val symclazz = sym.owner - if (symclazz == clazz && !pre.widen.isInstanceOf[TypeVar] && (pre.widen.typeSymbol isNonBottomSubClass symclazz)) { - // have to deconst because it may be a Class[T]. - pre.baseType(symclazz).deconst match { - case TypeRef(_, basesym, baseargs) => - - def instParam(ps: List[Symbol], as: List[Type]): Type = - if (ps.isEmpty) { - if (forInteractive) { - val saved = settings.uniqid.value - try { - settings.uniqid.value = true - println("*** stale type parameter: " + tp + sym.locationString + " cannot be instantiated from " + pre.widen) - println("*** confused with params: " + sym + " in " + sym.owner + " not in " + ps + " of " + basesym) - println("*** stacktrace = ") - new Error().printStackTrace() - } finally settings.uniqid.value = saved - instParamRelaxed(basesym.typeParams, baseargs) - } else throwError - } else if (sym eq ps.head) - // @M! don't just replace the whole thing, might be followed by type application - appliedType(as.head, args mapConserve (this)) // @M: was as.head - else instParam(ps.tail, as.tail) - - /** Relaxed version of instParams which matches on names not symbols. - * This is a last fallback in interactive mode because races in calls - * from the IDE to the compiler may in rare cases lead to symbols referring - * to type parameters that are no longer current. - */ - def instParamRelaxed(ps: List[Symbol], as: List[Type]): Type = - if (ps.isEmpty) throwError - else if (sym.name == ps.head.name) - // @M! don't just replace the whole thing, might be followed by type application - appliedType(as.head, args mapConserve (this)) // @M: was as.head - else instParamRelaxed(ps.tail, as.tail) - - //Console.println("instantiating " + sym + " from " + basesym + " with " + basesym.typeParams + " and " + baseargs+", pre = "+pre+", symclazz = "+symclazz);//DEBUG - if (sameLength(basesym.typeParams, baseargs)) - instParam(basesym.typeParams, baseargs) - else - if (symclazz.tpe.parents exists typeIsErroneous) - ErrorType // don't be to overzealous with throwing exceptions, see #2641 - else - throw new Error( - "something is wrong (wrong class file?): "+basesym+ - " with type parameters "+ - basesym.typeParams.map(_.name).mkString("[",",","]")+ - " gets applied to arguments "+baseargs.mkString("[",",","]")+", phase = "+phase) - case ExistentialType(tparams, qtpe) => - capturedSkolems = capturedSkolems union tparams - toInstance(qtpe, clazz) - case t => - throwError - } - } else toInstance(pre.baseType(clazz).prefix, clazz.owner) - } - toInstance(pre, clazz) - case _ => - mapOver(tp) + /** Find the type argument in an applied type which corresponds to a type parameter. + * The arguments are required to be related as follows, through intermediary `clazz`. + * An exception will be thrown if this is violated. + * + * @param lhs its symbol is a type parameter of `clazz` + * @param rhs a type application constructed from `clazz` + */ + private def correspondingTypeArgument(lhs: Type, rhs: Type): Type = { + val TypeRef(_, lhsSym, lhsArgs) = lhs + val TypeRef(_, rhsSym, rhsArgs) = rhs + require(lhsSym.safeOwner == rhsSym, s"$lhsSym is not a type parameter of $rhsSym") + + // Find the type parameter position; we'll use the corresponding argument + val argIndex = rhsSym.typeParams indexOf lhsSym + + if (argIndex >= 0 && argIndex < rhsArgs.length) // @M! don't just replace the whole thing, might be followed by type application + appliedType(rhsArgs(argIndex), lhsArgs mapConserve this) + else if (rhsSym.tpe_*.parents exists typeIsErroneous) // don't be too zealous with the exceptions, see #2641 + ErrorType + else + abort(s"something is wrong: cannot make sense of type application\n $lhs\n $rhs") + } + + // 0) @pre: `classParam` is a class type parameter + // 1) Walk the owner chain of `seenFromClass` until we find the class which owns `classParam` + // 2) Take the base type of the prefix at that point with respect to the owning class + // 3) Solve for the type parameters through correspondence with the type args of the base type + // + // Only class type parameters (and not skolems) are considered, because other type parameters + // are not influenced by the prefix through which they are seen. Note that type params of + // anonymous type functions, which currently can only arise from normalising type aliases, are + // owned by the type alias of which they are the eta-expansion. + private def classParameterAsSeen(classParam: Type): Type = { + val TypeRef(_, tparam, _) = classParam + + def loop(pre: Type, clazz: Symbol): Type = { + // have to deconst because it may be a Class[T] + def nextBase = (pre baseType clazz).deconst + //@M! see test pos/tcpoly_return_overriding.scala why mapOver is necessary + if (skipPrefixOf(pre, clazz)) + mapOver(classParam) + else if (!matchesPrefixAndClass(pre, clazz)(tparam.owner)) + loop(nextBase.prefix, clazz.owner) + else nextBase match { + case applied @ TypeRef(_, _, _) => correspondingTypeArgument(classParam, applied) + case ExistentialType(eparams, qtpe) => captureSkolems(eparams) ; loop(qtpe, clazz) + case t => abort(s"$tparam in ${tparam.owner} cannot be instantiated from ${seenFromPrefix.widen}") + } + } + loop(seenFromPrefix, seenFromClass) + } + + // Does the candidate symbol match the given prefix and class? + // Since pre may be something like ThisType(A) where trait A { self: B => }, + // we have to test the typeSymbol of the widened type, not pre.typeSymbol, or + // B will not be considered. + private def matchesPrefixAndClass(pre: Type, clazz: Symbol)(candidate: Symbol) = pre.widen match { + case _: TypeVar => false + case wide => (clazz == candidate) && (wide.typeSymbol isSubClass clazz) + } + + // Whether the annotation tree currently being mapped over has had a This(_) node rewritten. + private[this] var wroteAnnotation = false + private object annotationArgRewriter extends TypeMapTransformer { + private def matchesThis(thiz: Symbol) = matchesPrefixAndClass(seenFromPrefix, seenFromClass)(thiz) + + // what symbol should really be used? + private def newThis(): Tree = { + wroteAnnotation = true + val presym = seenFromPrefix.widen.typeSymbol + val thisSym = presym.owner.newValue(presym.name.toTermName, presym.pos) setInfo seenFromPrefix + gen.mkAttributedQualifier(seenFromPrefix, thisSym) + } + + /** Rewrite `This` trees in annotation argument trees */ + override def transform(tree: Tree): Tree = super.transform(tree) match { + case This(_) if matchesThis(tree.symbol) => newThis() + case tree => tree } + } + + // This becomes considerably cheaper if we optimize for the common cases: + // where the prefix is stable and where no This nodes are rewritten. If + // either is true, then we don't need to worry about calling giveup. So if + // the prefix is unstable, use a stack variable to indicate whether the tree + // was touched. This takes us to one allocation per AsSeenFromMap rather + // than an allocation on every call to mapOver, and no extra work when the + // tree only has its types remapped. + override def mapOver(tree: Tree, giveup: ()=>Nothing): Tree = { + if (isStablePrefix) + annotationArgRewriter transform tree + else { + val saved = wroteAnnotation + wroteAnnotation = false + try annotationArgRewriter transform tree + finally if (wroteAnnotation) giveup() else wroteAnnotation = saved + } + } + + private def thisTypeAsSeen(tp: ThisType): Type = { + def loop(pre: Type, clazz: Symbol): Type = { + val pre1 = pre match { + case SuperType(thistpe, _) => thistpe + case _ => pre + } + if (skipPrefixOf(pre, clazz)) + mapOver(tp) // TODO - is mapOver necessary here? + else if (!matchesPrefixAndClass(pre, clazz)(tp.sym)) + loop((pre baseType clazz).prefix, clazz.owner) + else if (pre1.isStable) + pre1 + else + captureThis(pre1, clazz) + } + loop(seenFromPrefix, seenFromClass) + } + + private def singleTypeAsSeen(tp: SingleType): Type = { + val SingleType(pre, sym) = tp + + val pre1 = this(pre) + if (pre1 eq pre) tp + else if (pre1.isStable) singleType(pre1, sym) + else pre1.memberType(sym).resultType //todo: this should be rolled into existential abstraction + } + + override def toString = s"AsSeenFromMap($seenFromPrefix, $seenFromClass)" } /** A base class to compute all substitutions */ @@ -5680,44 +5746,41 @@ trait Types extends api.Types { self: SymbolTable => case _ => false } - // @assume tp1.isHigherKinded || tp2.isHigherKinded - def isHKSubType0(tp1: Type, tp2: Type, depth: Int): Boolean = ( - tp1.typeSymbol == NothingClass - || - tp2.typeSymbol == AnyClass // @M Any and Nothing are super-type resp. subtype of every well-kinded type - || // @M! normalize reduces higher-kinded case to PolyType's - ((tp1.normalize.withoutAnnotations , tp2.normalize.withoutAnnotations) match { - case (PolyType(tparams1, res1), PolyType(tparams2, res2)) => // @assume tp1.isHigherKinded && tp2.isHigherKinded (as they were both normalized to PolyType) - sameLength(tparams1, tparams2) && { - if (tparams1.head.owner.isMethod) { // fast-path: polymorphic method type -- type params cannot be captured - (tparams1 corresponds tparams2)((p1, p2) => p2.info.substSym(tparams2, tparams1) <:< p1.info) && - res1 <:< res2.substSym(tparams2, tparams1) - } else { // normalized higher-kinded type - //@M for an example of why we need to generate fresh symbols, see neg/tcpoly_ticket2101.scala - val tpsFresh = cloneSymbols(tparams1) - - (tparams1 corresponds tparams2)((p1, p2) => - p2.info.substSym(tparams2, tpsFresh) <:< p1.info.substSym(tparams1, tpsFresh)) && - res1.substSym(tparams1, tpsFresh) <:< res2.substSym(tparams2, tpsFresh) - - //@M the forall in the previous test could be optimised to the following, - // but not worth the extra complexity since it only shaves 1s from quick.comp - // (List.forall2(tpsFresh/*optimisation*/, tparams2)((p1, p2) => - // p2.info.substSym(tparams2, tpsFresh) <:< p1.info /*optimisation, == (p1 from tparams1).info.substSym(tparams1, tpsFresh)*/) && - // this optimisation holds because inlining cloneSymbols in `val tpsFresh = cloneSymbols(tparams1)` gives: - // val tpsFresh = tparams1 map (_.cloneSymbol) - // for (tpFresh <- tpsFresh) tpFresh.setInfo(tpFresh.info.substSym(tparams1, tpsFresh)) - } - } && annotationsConform(tp1.normalize, tp2.normalize) + private def isPolySubType(tp1: PolyType, tp2: PolyType): Boolean = { + val PolyType(tparams1, res1) = tp1 + val PolyType(tparams2, res2) = tp2 - case (PolyType(_, _), MethodType(params, _)) if params exists (_.tpe.isWildcard) => - false // don't warn on HasMethodMatching on right hand side + sameLength(tparams1, tparams2) && { + // fast-path: polymorphic method type -- type params cannot be captured + val isMethod = tparams1.head.owner.isMethod + //@M for an example of why we need to generate fresh symbols otherwise, see neg/tcpoly_ticket2101.scala + val substitutes = if (isMethod) tparams1 else cloneSymbols(tparams1) + def sub1(tp: Type) = if (isMethod) tp else tp.substSym(tparams1, substitutes) + def sub2(tp: Type) = tp.substSym(tparams2, substitutes) + def cmp(p1: Symbol, p2: Symbol) = sub2(p2.info) <:< sub1(p1.info) - case (ntp1, ntp2) => - devWarning(s"isHKSubType0($tp1, $tp2, _) is ${tp1.getClass}, ${tp2.getClass}: ($ntp1, $ntp2)") - false // @assume !tp1.isHigherKinded || !tp2.isHigherKinded - // --> thus, cannot be subtypes (Any/Nothing has already been checked) - })) + (tparams1 corresponds tparams2)(cmp) && (sub1(res1) <:< sub2(res2)) + } + } + + // @assume tp1.isHigherKinded || tp2.isHigherKinded + def isHKSubType(tp1: Type, tp2: Type, depth: Int): Boolean = { + def isSub(ntp1: Type, ntp2: Type) = (ntp1.withoutAnnotations, ntp2.withoutAnnotations) match { + case (TypeRef(_, AnyClass, _), _) => false // avoid some warnings when Nothing/Any are on the other side + case (_, TypeRef(_, NothingClass, _)) => false + case (pt1: PolyType, pt2: PolyType) => isPolySubType(pt1, pt2) // @assume both .isHigherKinded (both normalized to PolyType) + case (_: PolyType, MethodType(ps, _)) if ps exists (_.tpe.isWildcard) => false // don't warn on HasMethodMatching on right hand side + case _ => // @assume !(both .isHigherKinded) thus cannot be subtypes + def tp_s(tp: Type): String = f"$tp%-20s ${util.shortClassOfInstance(tp)}%s" + devWarning(s"HK subtype check on $tp1 and $tp2, but both don't normalize to polytypes:\n tp1=${tp_s(ntp1)}\n tp2=${tp_s(ntp2)}") + false + } + + ( tp1.typeSymbol == NothingClass // @M Nothing is subtype of every well-kinded type + || tp2.typeSymbol == AnyClass // @M Any is supertype of every well-kinded type (@PP: is it? What about continuations plugin?) + || isSub(tp1.normalize, tp2.normalize) && annotationsConform(tp1, tp2) // @M! normalize reduces higher-kinded case to PolyType's + ) + } def isSubArgs(tps1: List[Type], tps2: List[Type], tparams: List[Symbol], depth: Int): Boolean = { def isSubArg(t1: Type, t2: Type, variance: Variance) = ( @@ -5735,7 +5798,7 @@ trait Types extends api.Types { self: SymbolTable => if (tp1 eq NoPrefix) return (tp2 eq NoPrefix) || tp2.typeSymbol.isPackageClass // !! I do not see how the "isPackageClass" would be warranted by the spec if (tp2 eq NoPrefix) return tp1.typeSymbol.isPackageClass if (isSingleType(tp1) && isSingleType(tp2) || isConstantType(tp1) && isConstantType(tp2)) return tp1 =:= tp2 - if (tp1.isHigherKinded || tp2.isHigherKinded) return isHKSubType0(tp1, tp2, depth) + if (tp1.isHigherKinded || tp2.isHigherKinded) return isHKSubType(tp1, tp2, depth) /** First try, on the right: * - unwrap Annotated types, BoundedWildcardTypes, diff --git a/test/files/neg/eta-expand-star-deprecation.check b/test/files/neg/eta-expand-star-deprecation.check new file mode 100644 index 0000000000..a79f0df76c --- /dev/null +++ b/test/files/neg/eta-expand-star-deprecation.check @@ -0,0 +1,4 @@ +warning: -Yeta-expand-keeps-star is deprecated: This flag is scheduled for removal in 2.12. If you have a case where you need this flag then please report a bug. +error: No warnings can be incurred under -Xfatal-warnings. +one warning found +one error found diff --git a/test/files/neg/eta-expand-star-deprecation.flags b/test/files/neg/eta-expand-star-deprecation.flags new file mode 100644 index 0000000000..5ac8b638e4 --- /dev/null +++ b/test/files/neg/eta-expand-star-deprecation.flags @@ -0,0 +1 @@ +-Yeta-expand-keeps-star -deprecation -Xfatal-warnings diff --git a/test/files/neg/eta-expand-star-deprecation.scala b/test/files/neg/eta-expand-star-deprecation.scala new file mode 100644 index 0000000000..5749692522 --- /dev/null +++ b/test/files/neg/eta-expand-star-deprecation.scala @@ -0,0 +1,8 @@ +object Test { + def f[T](xs: T*): Unit = () + def g[T] = f[T] _ + + def main(args: Array[String]): Unit = { + g(1, 2) + } +} |