From c141254b97a48e47ed7a6bfa08922671cc639081 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 9 Aug 2016 15:42:44 +1000 Subject: Determistically enter classes from directory into package scope On Linux, the directory listing is not automatically sorted on Mac. This leads to non-determistic ids of Symbols of the classes in a directory, which in turn leads to instability of the ordering of parents within inferred refinement types. Notable, with this patch, we will stably infer: ``` scala> case class C(); case class D(); List(C(), D()).head defined class C defined class D res0: Product with Serializable = C() ``` rather than sometimes getting `Serializable with Product` on Linux. As such, I've removed the workarounds for this instability in two test cases. --- test/files/presentation/callcc-interpreter/Runner.scala | 5 +---- test/files/run/t7747-repl.scala | 4 +--- 2 files changed, 2 insertions(+), 7 deletions(-) (limited to 'test/files') diff --git a/test/files/presentation/callcc-interpreter/Runner.scala b/test/files/presentation/callcc-interpreter/Runner.scala index a5698be5c2..1c03e3d5ba 100644 --- a/test/files/presentation/callcc-interpreter/Runner.scala +++ b/test/files/presentation/callcc-interpreter/Runner.scala @@ -1,6 +1,3 @@ import scala.tools.nsc.interactive.tests._ -object Test extends InteractiveTest { - // Normalize ordering of LUB - override def normalize(s: String) = s.replace("Serializable with Product", "Product with Serializable") -} +object Test extends InteractiveTest diff --git a/test/files/run/t7747-repl.scala b/test/files/run/t7747-repl.scala index 0094d3ba98..8203f4c802 100644 --- a/test/files/run/t7747-repl.scala +++ b/test/files/run/t7747-repl.scala @@ -10,9 +10,7 @@ object Test extends ReplTest { override def normalize(s: String) = { // replace indylambda function names by - val s2 = """\$Lambda.*""".r.replaceAllIn(s, "") - // Normalize ordering of LUB - s2.replace("Serializable with Product", "Product with Serializable") + """\$Lambda.*""".r.replaceAllIn(s, "") } def code = """ -- cgit v1.2.3 From a56afcd6192ae69633df23137576505015d34992 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Mon, 18 Jul 2016 11:29:44 +1000 Subject: Type#contains should peer into RefinementTypeRef-s Usually, `contains` should not look into class symbol infos. For instance, we expect that: ``` scala> trait C { def foo: Int }; typeOf[C].contains(IntClass) defined trait C res1: Boolean = false ``` We do, however, look at the decls of a `RefinedType` in contains: ``` scala> typeOf[{ def foo: Int }].contains(IntClass) res2: Boolean = true ``` Things get a little vague, however, when we consider a type ref to the refinement class symbol of a refined type. ``` scala> TypeRef(NoPrefix, typeOf[{ def foo: Int }].typeSymbol, Nil) res3: $r.intp.global.Type = AnyRef{def foo: Int} scala> .contains(IntClass) res4: Boolean = false ``` These show up in the first element of the base type seq of a refined type, e.g: ``` scala> typeOf[{ def foo: Int }].typeSymbol.tpe_* res5: $r.intp.global.Type = AnyRef{def foo: Int} scala> typeOf[{ def foo: Int }].baseTypeSeq(0).getClass res7: Class[_ <: $r.intp.global.Type] = class scala.reflect.internal.Types$RefinementTypeRef scala> typeOf[{ def foo: Int }].typeSymbol.tpe_*.getClass res6: Class[_ <: $r.intp.global.Type] = class scala.reflect.internal.Types$RefinementTypeRef ``` This commit takes the opinion that a `RefinementTypeRef` should be transparent with respect to `contains`. This paves the way for fixing the base type sequences of existential types over refinement types. The implementation of `ContainsCollector` was already calling `normalize`, which goes from `RefinementTypeRef` to `RefinedType`. This commit maps over the result, which looks in the parents and decls. --- .../scala/tools/nsc/typechecker/MethodSynthesis.scala | 19 +++++++++++++------ src/reflect/scala/reflect/internal/tpe/TypeMaps.scala | 3 +++ test/files/pos/typevar-in-prefix.scala | 9 +++++++++ test/junit/scala/reflect/internal/TypesTest.scala | 11 +++++++++++ 4 files changed, 36 insertions(+), 6 deletions(-) create mode 100644 test/files/pos/typevar-in-prefix.scala (limited to 'test/files') diff --git a/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala b/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala index 99ef4ed373..e0b64a7600 100644 --- a/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala +++ b/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala @@ -321,13 +321,20 @@ trait MethodSynthesis { // starts compiling (instead of failing like it's supposed to) because the typer // expects to be able to identify escaping locals in typedDefDef, and fails to // spot that brand of them. In other words it's an artifact of the implementation. + // + // JZ: ... or we could go back to uniformly using explicit result types in all cases + // if we fix `dropExistential`. More details https://github.com/scala/scala-dev/issues/165 val getterTp = derivedSym.tpe_*.finalResultType - val tpt = getterTp.widen match { - // Range position errors ensue if we don't duplicate this in some - // circumstances (at least: concrete vals with existential types.) - case _: ExistentialType => TypeTree() setOriginal (tree.tpt.duplicate setPos tree.tpt.pos.focus) - case _ if tree.mods.isDeferred => TypeTree() setOriginal tree.tpt // keep type tree of original abstract field - case _ => TypeTree(getterTp) + // Range position errors ensue if we don't duplicate this in some + // circumstances (at least: concrete vals with existential types.) + def inferredTpt = TypeTree() setOriginal (tree.tpt.duplicate setPos tree.tpt.pos.focus) + val tpt = getterTp match { + case _: ExistentialType => inferredTpt + case _ => getterTp.widen match { + case _: ExistentialType => inferredTpt + case _ if tree.mods.isDeferred => TypeTree() setOriginal tree.tpt // keep type tree of original abstract field + case _ => TypeTree(getterTp) + } } tpt setPos tree.tpt.pos.focus } diff --git a/src/reflect/scala/reflect/internal/tpe/TypeMaps.scala b/src/reflect/scala/reflect/internal/tpe/TypeMaps.scala index c3f92f1bce..24f83c0b99 100644 --- a/src/reflect/scala/reflect/internal/tpe/TypeMaps.scala +++ b/src/reflect/scala/reflect/internal/tpe/TypeMaps.scala @@ -1012,6 +1012,9 @@ private[internal] trait TypeMaps { case _ => tp.normalize match { case TypeRef(_, sym1, _) if (sym == sym1) => result = true + case refined: RefinedType => + mapOver(tp.prefix) + mapOver(refined) case SingleType(_, sym1) if (sym == sym1) => result = true case _ => mapOver(tp) } diff --git a/test/files/pos/typevar-in-prefix.scala b/test/files/pos/typevar-in-prefix.scala new file mode 100644 index 0000000000..929648b789 --- /dev/null +++ b/test/files/pos/typevar-in-prefix.scala @@ -0,0 +1,9 @@ +trait Test1 { + abstract class Setting + def Bool: Setting + + class C[T <: Setting](val s: T) + val setting1 = null.asInstanceOf[_1.s.type forSome { val _1: C[Setting] }] + // the derived accessor for this val was not using an inferred type, as was + // the intention of the implementation in MethodSynthesis. +} diff --git a/test/junit/scala/reflect/internal/TypesTest.scala b/test/junit/scala/reflect/internal/TypesTest.scala index 05a77cfb47..45a4369396 100644 --- a/test/junit/scala/reflect/internal/TypesTest.scala +++ b/test/junit/scala/reflect/internal/TypesTest.scala @@ -58,4 +58,15 @@ class TypesTest { Assert.fail(xs.mkString("\n")) } } + + @Test + def testRefinementContains(): Unit = { + val refinement = typeOf[{def foo: Int}] + assert(refinement.isInstanceOf[RefinedType]) + assert(refinement.contains(IntClass)) + val elem0 = refinement.baseTypeSeq(0) + assert(elem0.isInstanceOf[RefinementTypeRef]) + assert(elem0.contains(IntClass)) + } + } -- cgit v1.2.3 From 56f23af90732b3770770139b33f92852384c9f04 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Mon, 18 Jul 2016 11:29:44 +1000 Subject: Improved refinement type and existential type handling Lazy base type seq elements are encoded as a refined type with an empty scope and a list of type refs over some common type symbol that will be merged when `BaseTypeSeq#apply` is called. The first change in this commit is to mark the creation and consumption of such elements with calls to `[is]IntersectionTypeForBaseTypeSeq`. They are distinguished by using the actual type symbol rather than a refinement class symbol, which in turn simplifies the code in `BaseTypeSeq#typeSymbol`. I have also made `lub` aware of this encoding: it is now able to "see through" to the parents of such refined types and merge them with other base types of the same class symbol (even other refined types representing lazy BTS elements.) To make this fix work, I also had to fix a bug in LUBs of multiple with existential types. Because of the way the recursion was structured in `mergePrefixAndArgs`, the order of list of types being merged changed behaviour: quantified varialbles of existential types were being rewrapped around the resultting type, but only if we hadn't encountered the first regular `TypeRef`. This can be seen with the following before/after shot: ``` // 2.11.8 scala> val ts = typeOf[Set[Any]] :: typeOf[Set[X] forSome { type X <: Y; type Y <: Int}] :: Nil; def merge(ts: List[Type]) = mergePrefixAndArgs(ts, Variance.Contravariant, lubDepth(ts)); val merged1 = merge(ts); val merged2 = merge(ts.reverse); (ts.forall(_ <:< merged1), ts.forall(_ <:< merged2)) ts: List[$r.intp.global.Type] = List(Set[Any], Set[_ <: Int]) merge: (ts: List[$r.intp.global.Type])$r.intp.global.Type merged1: $r.intp.global.Type = scala.collection.immutable.Set[_ >: Int] merged2: $r.intp.global.Type = scala.collection.immutable.Set[_53] forSome { type X <: Int; type _53 >: X } res0: (Boolean, Boolean) = (false,true) // HEAD ... merged1: $r.intp.global.Type = scala.collection.immutable.Set[_10] forSome { type X <: Int; type _10 >: X } merged2: $r.intp.global.Type = scala.collection.immutable.Set[_11] forSome { type X <: Int; type _11 >: X } res0: (Boolean, Boolean) = (true,true) ``` Furthermore, I have fixed the computation of the base type sequences of existential types over refinement types, in order to maintain the invariant that each slot of the base type sequence of a existential has the same type symbol as that of its underlying type. Before, what I've now called a `RefinementTypeRef` was transformed into a `RefinedType` during rewrapping in the existential, which led to it being wrongly considered as a lazy element of the base type sequence. The first change above should also be sufficient to avoid the bug, but I felt it was worth cleaning up `maybeRewrap` as an extra line of defence. Finally, I have added another special case to `BaseTypeSeq#apply` to be able to lazily compute elements that have been wrapped in an existential. The unit test cases in `TypesTest` rely on these changes. A subsequent commit will build on this foundation to make a fix to `asSeenFrom`. --- .../scala/tools/nsc/backend/jvm/opt/Inliner.scala | 1 + .../scala/reflect/internal/BaseTypeSeqs.scala | 62 ++++---- src/reflect/scala/reflect/internal/Types.scala | 166 ++++++++++++--------- .../scala/reflect/internal/tpe/GlbLubs.scala | 2 +- test/files/neg/lub-from-hell-2.check | 7 + test/files/neg/lub-from-hell-2.scala | 6 + test/files/pos/lub-from-hell.scala | 11 ++ test/junit/scala/reflect/internal/TypesTest.scala | 59 ++++++++ 8 files changed, 211 insertions(+), 103 deletions(-) create mode 100644 test/files/neg/lub-from-hell-2.check create mode 100644 test/files/neg/lub-from-hell-2.scala create mode 100644 test/files/pos/lub-from-hell.scala (limited to 'test/files') diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala index b7523bbf06..64638ca34d 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala @@ -142,6 +142,7 @@ class Inliner[BT <: BTypes](val btypes: BT) { elided += r else result += r + () } result.toList } diff --git a/src/reflect/scala/reflect/internal/BaseTypeSeqs.scala b/src/reflect/scala/reflect/internal/BaseTypeSeqs.scala index 81281b5eb4..0f5b800925 100644 --- a/src/reflect/scala/reflect/internal/BaseTypeSeqs.scala +++ b/src/reflect/scala/reflect/internal/BaseTypeSeqs.scala @@ -56,42 +56,44 @@ trait BaseTypeSeqs { if(pending contains i) { pending.clear() throw CyclicInheritance - } else - elems(i) match { - case rtp @ RefinedType(variants, decls) => - // can't assert decls.isEmpty; see t0764 - //if (!decls.isEmpty) abort("computing closure of "+this+":"+this.isInstanceOf[RefinedType]+"/"+closureCache(j)) - //Console.println("compute closure of "+this+" => glb("+variants+")") - pending += i - try { - mergePrefixAndArgs(variants, Variance.Contravariant, lubDepth(variants)) match { - case NoType => typeError("no common type instance of base types "+(variants mkString ", and ")+" exists.") - case tp0 => - pending(i) = false - elems(i) = tp0 - tp0 - } - } - catch { - case CyclicInheritance => - typeError( - "computing the common type instance of base types "+(variants mkString ", and ")+" leads to a cycle.") + } else { + def computeLazyType(rtp: RefinedType): Type = { + if (!isIntersectionTypeForLazyBaseType(rtp)) + abort("unexpected RefinedType in base type seq, lazy BTS elements should be created via intersectionTypeForLazyBaseType: " + rtp) + val variants = rtp.parents + // can't assert decls.isEmpty; see t0764 + //if (!decls.isEmpty) abort("computing closure of "+this+":"+this.isInstanceOf[RefinedType]+"/"+closureCache(j)) + //Console.println("compute closure of "+this+" => glb("+variants+")") + pending += i + try { + mergePrefixAndArgs(variants, Variance.Contravariant, lubDepth(variants)) match { + case NoType => typeError("no common type instance of base types " + (variants mkString ", and ") + " exists.") + case tp0 => + pending(i) = false + elems(i) = tp0 + tp0 } + } + catch { + case CyclicInheritance => + typeError( + "computing the common type instance of base types " + (variants mkString ", and ") + " leads to a cycle.") + } + } + elems(i) match { + case rtp@RefinedType(variants, decls) => + computeLazyType(rtp) + case et @ ExistentialType(quantified, rtp: RefinedType) => + existentialAbstraction(quantified, computeLazyType(rtp)) case tp => tp } + } def rawElem(i: Int) = elems(i) - /** The type symbol of the type at i'th position in this sequence; - * no evaluation needed. - */ - def typeSymbol(i: Int): Symbol = { - elems(i) match { - case RefinedType(v :: vs, _) => v.typeSymbol - case tp => tp.typeSymbol - } - } + /** The type symbol of the type at i'th position in this sequence */ + def typeSymbol(i: Int): Symbol = elems(i).typeSymbol /** Return all evaluated types in this sequence as a list */ def toList: List[Type] = elems.toList @@ -215,7 +217,7 @@ trait BaseTypeSeqs { } i += 1 } - buf += intersectionType(minTypes) + buf += intersectionTypeForLazyBaseType(minTypes) // TODO this reverses the order. Does this matter? Or should this be minTypes.reverse? btsSize += 1 } } diff --git a/src/reflect/scala/reflect/internal/Types.scala b/src/reflect/scala/reflect/internal/Types.scala index 7dda805378..523cb968e7 100644 --- a/src/reflect/scala/reflect/internal/Types.scala +++ b/src/reflect/scala/reflect/internal/Types.scala @@ -174,8 +174,10 @@ trait Types if (newtp eq underlying) this // BoundedWildcardTypes reach here during erroneous compilation: neg/t6258 // Higher-kinded exclusion is because [x]CC[x] compares =:= to CC: pos/t3800 + // Avoid reusing the existing Wrapped(RefinedType) when we've be asked to wrap an =:= RefinementTypeRef, the + // distinction is important in base type sequences. // Otherwise, if newtp =:= underlying, don't rewrap it. - else if (!newtp.isWildcard && !newtp.isHigherKinded && (newtp =:= underlying)) this + else if (!newtp.isWildcard && !newtp.isHigherKinded && !newtp.isInstanceOf[RefinementTypeRef] && (newtp =:= underlying)) this else rewrap(newtp) ) protected def rewrap(newtp: Type): Type @@ -1589,7 +1591,6 @@ trait Types */ case class RefinedType(override val parents: List[Type], override val decls: Scope) extends CompoundType with RefinedTypeApi { - override def isHigherKinded = ( parents.nonEmpty && (parents forall typeIsHigherKinded) && @@ -2704,6 +2705,7 @@ trait Types def isRepresentableWithWildcards = { val qset = quantified.toSet underlying match { + case _: RefinementTypeRef => false case TypeRef(pre, sym, args) => def isQuantified(tpe: Type): Boolean = { (tpe exists (t => qset contains t.typeSymbol)) || @@ -3521,7 +3523,9 @@ trait Types if ((parents eq original.parents) && (decls eq original.decls)) original else { val owner = original.typeSymbol.owner - val result = refinedType(parents, owner) + val result = + if (isIntersectionTypeForLazyBaseType(original)) intersectionTypeForLazyBaseType(parents) + else refinedType(parents, owner) val syms1 = decls.toList for (sym <- syms1) result.decls.enter(sym.cloneSymbol(result.typeSymbol)) @@ -3596,6 +3600,14 @@ trait Types case tp :: Nil => tp case _ => refinedType(tps, commonOwner(tps)) } + def intersectionTypeForLazyBaseType(tps: List[Type]) = tps match { + case tp :: Nil => tp + case _ => RefinedType(tps, newScope, tps.head.typeSymbolDirect) + } + def isIntersectionTypeForLazyBaseType(tp: RefinedType) = tp.parents match { + case head :: _ => tp.typeSymbolDirect eq head.typeSymbolDirect + case _ => false + } /**** This implementation to merge parents was checked in in commented-out form and has languished unaltered for five years. I think we should @@ -4409,83 +4421,93 @@ trait Types * Return `x` if the computation succeeds with result `x`. * Return `NoType` if the computation fails. */ - def mergePrefixAndArgs(tps: List[Type], variance: Variance, depth: Depth): Type = tps match { - case tp :: Nil => tp - case TypeRef(_, sym, _) :: rest => - val pres = tps map (_.prefix) // prefix normalizes automatically + def mergePrefixAndArgs(tps0: List[Type], variance: Variance, depth: Depth): Type = { + var tparams = mutable.ListBuffer[Symbol]() + val tps = tps0.flatMap { + case rt: RefinedType if isIntersectionTypeForLazyBaseType(rt) => rt.parents + case ExistentialType(qs, underlying) => + tparams ++= qs + underlying match { + case rt: RefinedType if isIntersectionTypeForLazyBaseType(rt) => rt.parents + case tp => tp :: Nil + } + case tp => tp :: Nil + } + + val merged = tps match { + case tp :: Nil => tp + case TypeRef(_, sym, _) :: rest => + val pres = tps map (_.prefix) // prefix normalizes automatically val pre = if (variance.isPositive) lub(pres, depth) else glb(pres, depth) - val argss = tps map (_.normalize.typeArgs) // symbol equality (of the tp in tps) was checked using typeSymbol, which normalizes, so should normalize before retrieving arguments + val argss = tps map (_.normalize.typeArgs) // symbol equality (of the tp in tps) was checked using typeSymbol, which normalizes, so should normalize before retrieving arguments val capturedParams = new ListBuffer[Symbol] - try { - if (sym == ArrayClass && phase.erasedTypes) { - // special treatment for lubs of array types after erasure: - // if argss contain one value type and some other type, the lub is Object - // if argss contain several reference types, the lub is an array over lub of argtypes - if (argss exists typeListIsEmpty) { - NoType // something is wrong: an array without a type arg. - } - else { - val args = argss map (_.head) - if (args.tail forall (_ =:= args.head)) typeRef(pre, sym, List(args.head)) - else if (args exists (arg => isPrimitiveValueClass(arg.typeSymbol))) ObjectTpe - else typeRef(pre, sym, List(lub(args))) + try { + if (sym == ArrayClass && phase.erasedTypes) { + // special treatment for lubs of array types after erasure: + // if argss contain one value type and some other type, the lub is Object + // if argss contain several reference types, the lub is an array over lub of argtypes + if (argss exists typeListIsEmpty) { + NoType // something is wrong: an array without a type arg. + } + else { + val args = argss map (_.head) + if (args.tail forall (_ =:= args.head)) typeRef(pre, sym, List(args.head)) + else if (args exists (arg => isPrimitiveValueClass(arg.typeSymbol))) ObjectTpe + else typeRef(pre, sym, List(lub(args))) + } } - } - else transposeSafe(argss) match { - case None => - // transpose freaked out because of irregular argss - // catching just in case (shouldn't happen, but also doesn't cost us) - // [JZ] It happens: see SI-5683. - debuglog(s"transposed irregular matrix!? tps=$tps argss=$argss") - NoType - case Some(argsst) => - val args = map2(sym.typeParams, argsst) { (tparam, as0) => - val as = as0.distinct - if (as.size == 1) as.head - else if (depth.isZero) { - log("Giving up merging args: can't unify %s under %s".format(as.mkString(", "), tparam.fullLocationString)) - // Don't return "Any" (or "Nothing") when we have to give up due to - // recursion depth. Return NoType, which prevents us from poisoning - // lublist's results. It can recognize the recursion and deal with it, but - // only if we aren't returning invalid types. - NoType - } - else { - if (tparam.variance == variance) lub(as, depth.decr) - else if (tparam.variance == variance.flip) glb(as, depth.decr) + else transposeSafe(argss) match { + case None => + // transpose freaked out because of irregular argss + // catching just in case (shouldn't happen, but also doesn't cost us) + // [JZ] It happens: see SI-5683. + debuglog(s"transposed irregular matrix!? tps=$tps argss=$argss") + NoType + case Some(argsst) => + val args = map2(sym.typeParams, argsst) { (tparam, as0) => + val as = as0.distinct + if (as.size == 1) as.head + else if (depth.isZero) { + log("Giving up merging args: can't unify %s under %s".format(as.mkString(", "), tparam.fullLocationString)) + // Don't return "Any" (or "Nothing") when we have to give up due to + // recursion depth. Return NoType, which prevents us from poisoning + // lublist's results. It can recognize the recursion and deal with it, but + // only if we aren't returning invalid types. + NoType + } else { - val l = lub(as, depth.decr) - val g = glb(as, depth.decr) - if (l <:< g) l - else { // Martin: I removed this, because incomplete. Not sure there is a good way to fix it. For the moment we - // just err on the conservative side, i.e. with a bound that is too high. - // if(!(tparam.info.bounds contains tparam)) //@M can't deal with f-bounds, see #2251 - - val qvar = commonOwner(as) freshExistential "" setInfo TypeBounds(g, l) - capturedParams += qvar - qvar.tpe + if (tparam.variance == variance) lub(as, depth.decr) + else if (tparam.variance == variance.flip) glb(as, depth.decr) + else { + val l = lub(as, depth.decr) + val g = glb(as, depth.decr) + if (l <:< g) l + else { // Martin: I removed this, because incomplete. Not sure there is a good way to fix it. For the moment we + // just err on the conservative side, i.e. with a bound that is too high. + // if(!(tparam.info.bounds contains tparam)) //@M can't deal with f-bounds, see #2251 + + val qvar = commonOwner(as) freshExistential "" setInfo TypeBounds(g, l) + capturedParams += qvar + qvar.tpe + } } } } - } - if (args contains NoType) NoType - else existentialAbstraction(capturedParams.toList, typeRef(pre, sym, args)) + if (args contains NoType) NoType + else existentialAbstraction(capturedParams.toList, typeRef(pre, sym, args)) + } + } catch { + case ex: MalformedType => NoType } - } catch { - case ex: MalformedType => NoType - } - case SingleType(_, sym) :: rest => - val pres = tps map (_.prefix) - val pre = if (variance.isPositive) lub(pres, depth) else glb(pres, depth) - try singleType(pre, sym) - catch { case ex: MalformedType => NoType } - case ExistentialType(tparams, quantified) :: rest => - mergePrefixAndArgs(quantified :: rest, variance, depth) match { - case NoType => NoType - case tpe => existentialAbstraction(tparams, tpe) - } - case _ => - abort(s"mergePrefixAndArgs($tps, $variance, $depth): unsupported tps") + case SingleType(_, sym) :: rest => + val pres = tps map (_.prefix) + val pre = if (variance.isPositive) lub(pres, depth) else glb(pres, depth) + try singleType(pre, sym) + catch { case ex: MalformedType => NoType } + case _ => + abort(s"mergePrefixAndArgs($tps, $variance, $depth): unsupported tps") + } + existentialAbstraction(tparams.toList, merged) } def addMember(thistp: Type, tp: Type, sym: Symbol): Unit = addMember(thistp, tp, sym, AnyDepth) diff --git a/src/reflect/scala/reflect/internal/tpe/GlbLubs.scala b/src/reflect/scala/reflect/internal/tpe/GlbLubs.scala index 123b44aa05..c997dd30eb 100644 --- a/src/reflect/scala/reflect/internal/tpe/GlbLubs.scala +++ b/src/reflect/scala/reflect/internal/tpe/GlbLubs.scala @@ -118,7 +118,7 @@ private[internal] trait GlbLubs { // ts0 is the 1-dimensional frontier of symbols cutting through 2-dimensional tsBts. // Invariant: all symbols "under" (closer to the first row) the frontier // are smaller (according to _.isLess) than the ones "on and beyond" the frontier - val ts0 = tsBts map (_.head) + val ts0 = tsBts map (_.head) // Is the frontier made up of types with the same symbol? val isUniformFrontier = (ts0: @unchecked) match { diff --git a/test/files/neg/lub-from-hell-2.check b/test/files/neg/lub-from-hell-2.check new file mode 100644 index 0000000000..3ef935f93b --- /dev/null +++ b/test/files/neg/lub-from-hell-2.check @@ -0,0 +1,7 @@ +lub-from-hell-2.scala:3: error: type arguments [Any,Iterable[Any] with Int => Any with scala.collection.generic.Subtractable[Any,Iterable[Any] with Int => Any with scala.collection.generic.Subtractable[Any,Iterable[Any] with Int => Any]{def seq: Iterable[Any] with Int => Any}]{def seq: Iterable[Any] with Int => Any{def seq: Iterable[Any] with Int => Any}}] do not conform to trait Subtractable's type parameter bounds [A,+Repr <: scala.collection.generic.Subtractable[A,Repr]] + def foo(a: Boolean, b: collection.mutable.Set[Any], c: collection.mutable.ListBuffer[Any]) = if (a) b else c + ^ +lub-from-hell-2.scala:4: error: type arguments [Any,scala.collection.mutable.Iterable[Any] with scala.collection.mutable.Cloneable[scala.collection.mutable.Iterable[Any] with scala.collection.mutable.Cloneable[scala.collection.mutable.Iterable[Any] with Cloneable with Int => Any] with Int => Any{def seq: scala.collection.mutable.Iterable[Any] with Cloneable with Int => Any}] with scala.collection.generic.Growable[Any] with Int => Any with scala.collection.generic.Shrinkable[Any] with scala.collection.generic.Subtractable[Any,Iterable[Any] with Int => Any with scala.collection.generic.Subtractable[Any,Iterable[Any] with Int => Any]{def seq: Iterable[Any] with Int => Any}] with scala.collection.script.Scriptable[Any]] do not conform to trait Subtractable's type parameter bounds [A,+Repr <: scala.collection.generic.Subtractable[A,Repr]] + def bar(a: Boolean, b: scala.collection.mutable.SetLike[Any,scala.collection.mutable.Set[Any]], c: scala.collection.mutable.Buffer[Any]) = if (a) b else c + ^ +two errors found diff --git a/test/files/neg/lub-from-hell-2.scala b/test/files/neg/lub-from-hell-2.scala new file mode 100644 index 0000000000..96760c6edf --- /dev/null +++ b/test/files/neg/lub-from-hell-2.scala @@ -0,0 +1,6 @@ +class Test { + trait Tree + def foo(a: Boolean, b: collection.mutable.Set[Any], c: collection.mutable.ListBuffer[Any]) = if (a) b else c + def bar(a: Boolean, b: scala.collection.mutable.SetLike[Any,scala.collection.mutable.Set[Any]], c: scala.collection.mutable.Buffer[Any]) = if (a) b else c + // bar produces an ill-bounded LUB in 2.11.8. After this commit, which fixes a bug in existential+refinement lubs, foo also fails. +} diff --git a/test/files/pos/lub-from-hell.scala b/test/files/pos/lub-from-hell.scala new file mode 100644 index 0000000000..a7d2a99b08 --- /dev/null +++ b/test/files/pos/lub-from-hell.scala @@ -0,0 +1,11 @@ +class Test { + trait Tree + def foo(b: Boolean, buf: collection.mutable.ArrayBuffer[Any], acc: StringBuilder) = if (b) buf else acc + + // def bar(b: Boolean, + // buf: scala.collection.IndexedSeqLike[Any,Cloneable with Mutable with Equals], + // acc: scala.collection.IndexedSeqLike[_18,scala.collection.mutable.IndexedSeq[_18]] with scala.collection.IndexedSeqLike[_18,IndexedSeq[_18]] forSome { type _18 >: String with Char } + // ) = if (b) buf else acc + + +} diff --git a/test/junit/scala/reflect/internal/TypesTest.scala b/test/junit/scala/reflect/internal/TypesTest.scala index 45a4369396..763fd73c8a 100644 --- a/test/junit/scala/reflect/internal/TypesTest.scala +++ b/test/junit/scala/reflect/internal/TypesTest.scala @@ -69,4 +69,63 @@ class TypesTest { assert(elem0.contains(IntClass)) } + @Test + def testRefinedLubs(): Unit = { + // https://github.com/scala/scala-dev/issues/168 + assertEquals(typeOf[Option[AnyVal]], lub(typeOf[Option[Int] with Option[Char]] :: typeOf[Option[Boolean] with Option[Short]] :: Nil)) + assertEquals(typeOf[Option[AnyVal]], lub(typeOf[Option[Int] with Option[Char]] :: typeOf[Option[Boolean]] :: Nil)) + assertEquals(typeOf[Option[AnyVal]], lub((typeOf[Option[Int] with Option[Char]] :: typeOf[Option[Boolean] with Option[Short]] :: Nil).reverse)) + assertEquals(typeOf[Option[AnyVal]], lub((typeOf[Option[Int] with Option[Char]] :: typeOf[Option[Boolean]] :: Nil).reverse)) + } + + @Test + def testExistentialRefinement(): Unit = { + import rootMirror.EmptyPackageClass + + // class M[A] + val MClass = EmptyPackageClass.newClass("M") + val A = MClass.newTypeParameter("A").setInfo(TypeBounds.empty) + MClass.setInfo(PolyType(A :: Nil, ClassInfoType(ObjectClass.tpeHK :: Nil, newScopeWith(), MClass))) + + // (M[Int] with M[X] { def m: Any }) forSome { type X } + val X = NoSymbol.newExistential("X").setInfo(TypeBounds.empty) + val T: Type = { + val decls = newScopeWith(MClass.newMethod("m").setInfo(NullaryMethodType(AnyClass.tpeHK))) + val refined = refinedType(appliedType(MClass, IntClass.tpeHK) :: appliedType(MClass, X.tpeHK) :: Nil, NoSymbol, decls, NoPosition) + newExistentialType(X :: Nil, refined) + } + + val RefinementClass = T.underlying.typeSymbol + assertTrue(RefinementClass.isRefinementClass) + TypeRef(NoPrefix, RefinementClass, Nil) match { + case rtr : RefinementTypeRef => + // ContainsCollector needs to look inside the info of symbols of RefinementTypeRefs + assert(rtr.contains(X)) + } + + val underlying = T.underlying + val baseTypeSeqIndices = T.baseTypeSeq.toList.indices + for (i <- baseTypeSeqIndices) { + // Elements of the existential type should have the same type symbol as underlying + assertEquals(T.baseTypeSeq.typeSymbol(i), underlying.baseTypeSeq.typeSymbol(i)) + } + + // Type symbols should be distinct + def checkDistinctTypeSyms(bts: BaseTypeSeq): Unit = { + val syms = baseTypeSeqIndices.map(T.baseTypeSeq.typeSymbol) + assertEquals(syms, syms.distinct) + } + checkDistinctTypeSyms(T.baseTypeSeq) + checkDistinctTypeSyms(T.underlying.baseTypeSeq) + + // This is the entry for the refinement class + assertTrue(T.baseTypeSeq.typeSymbol(0).isRefinementClass) + assertEquals("M[Int] with M[X]{def m: Any} forSome { type X }", T.baseTypeSeq.rawElem(0).toString) + + // This is the entry for M. The raw entry is an existential over a RefinedType which encodes a lazily computed base type + assertEquals(T.baseTypeSeq.typeSymbol(1), MClass) + assertEquals("M[X] with M[Int] forSome { type X }", T.baseTypeSeq.rawElem(1).toString) + // calling `apply` merges the prefix/args of the elements ot the RefinedType and rewraps in the existential + assertEquals("M[_1] forSome { type X; type _1 >: X with Int }", T.baseTypeSeq.apply(1).toString) + } } -- cgit v1.2.3 From 893acc1829b3be96c75d00189e9e1b94ff4bd848 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Mon, 18 Jul 2016 11:29:44 +1000 Subject: SI-5294 Use bounds of abstract prefix in asSeenFrom ASF was failing to recognize the correspondence between a prefix if it has an abstract type symbol, even if it is bounded by the currently considered class. Distilling the test cases, this led to incorrect typechecking of the RHS of `G` in: ``` trait T { type A trait HasH { type H[U] <: U } type F[N <: HasH] = N#H[T] type G[N <: HasH] = F[N]#A // RHS was incorrectly reduced to T.this.A } ``` In the fuller examples (included as test cases), this meant that type level functions written as members of `HList` could not be implemented in terms of each other, e.g. defining `Apply[N]` as `Drop[N]#Head` had the wrong semantics. This commit checks checks if the prefix has the candidate class as a base type, rather than checking if its type symbol has this as a base class. The latter formulation discarded information about the instantation of the abstract type. Using the example above: ``` scala> val F = typeOf[T].member(TypeName("F")).info F: $r.intp.global.Type = [N <: T.this.HasH]N#H[T] scala> F.resultType.typeSymbol.baseClasses // old approach res14: List[$r.intp.global.Symbol] = List(class Any) scala> F.resultType.baseClasses // new approach res13: List[$r.intp.global.Symbol] = List(trait T, class Object, class Any) ``` It is worth noting that dotty rejects some of these programs, as it introduces the rule that: > // 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. > final def isLegalPrefixFor(selector: Name)(implicit ctx: Context) However, typechecking the program above in this comment in dotty yields: trait T() extends Object { type A trait HasH() extends Object { type H <: [HK$0] => <: HK$0 } type F = [HK$0] => HK$0#H{HK$0 = T}#Apply type G = [HK$0] => HK$0#H{HK$0 = T}#Apply#A } As the equivalent code [1] in dotc's `asSeenFrom` already looks for a base type of the prefix, rather than looking for a superclass of the prefix's type symbol. [1] https://github.com/lampepfl/dotty/blob/d2c96d02fccef3a82b88ee1ff31253b6ef17f900/src/dotty/tools/dotc/core/TypeOps.scala#L62 --- .../scala/reflect/internal/tpe/TypeMaps.scala | 25 ++++++++++++--- test/files/pos/t5294b.scala | 36 ++++++++++++++++++++++ test/files/pos/t5294c.scala | 30 ++++++++++++++++++ test/files/pos/t6161b.scala | 22 +++++++++++++ test/files/run/t5294.scala | 22 +++++++++++++ test/pending/pos/t6161.scala | 22 ------------- 6 files changed, 130 insertions(+), 27 deletions(-) create mode 100644 test/files/pos/t5294b.scala create mode 100644 test/files/pos/t5294c.scala create mode 100644 test/files/pos/t6161b.scala create mode 100644 test/files/run/t5294.scala delete mode 100644 test/pending/pos/t6161.scala (limited to 'test/files') diff --git a/src/reflect/scala/reflect/internal/tpe/TypeMaps.scala b/src/reflect/scala/reflect/internal/tpe/TypeMaps.scala index 24f83c0b99..df61d2e418 100644 --- a/src/reflect/scala/reflect/internal/tpe/TypeMaps.scala +++ b/src/reflect/scala/reflect/internal/tpe/TypeMaps.scala @@ -610,11 +610,26 @@ private[internal] trait TypeMaps { } // 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) = - (clazz == candidate) && (pre.widen.typeSymbol isSubClass clazz) + private def matchesPrefixAndClass(pre: Type, clazz: Symbol)(candidate: Symbol) = (clazz == candidate) && { + val pre1 = pre match { + case tv: TypeVar => + // Needed with existentials in prefixes, e.g. test/files/pos/typevar-in-prefix.scala + // Perhaps the base type sequence of a type var should include it bounds? + tv.origin + case _ => pre + } + // widen needed (at least) because of https://github.com/scala/scala-dev/issues/166 + (clazz == candidate) && ( + if (clazz.isRefinementClass) + // base type seqs of aliases over refinement types have copied refinement types based on beta reduction + // for reliable lookup we need to consult the base type of the type symbol. (example: pos/t8177b.scala) + pre1.widen.typeSymbol isSubClass clazz + else + // In the general case, we look at the base type sequence of the prefix itself, + // which can have more concrete base classes than `.typeSymbol.baseClasses` (example: t5294, t6161) + pre1.widen.baseTypeIndex(clazz) != -1 + ) + } // Whether the annotation tree currently being mapped over has had a This(_) node rewritten. private[this] var wroteAnnotation = false diff --git a/test/files/pos/t5294b.scala b/test/files/pos/t5294b.scala new file mode 100644 index 0000000000..038d2ff806 --- /dev/null +++ b/test/files/pos/t5294b.scala @@ -0,0 +1,36 @@ +class Test { + def test = { + + val l1 = null: Int #: String #: Boolean #: String #: HNil.type + + type _2 = Succ[Succ[Zero.type]] + + val t1: Boolean = null.asInstanceOf[ l1.type#Drop[_2]#Head ] + + val t2: Boolean = null.asInstanceOf[ l1.type#Apply[_2] ] + } +} + + +sealed trait Nat { + type Fold[U, F[_ <: U] <: U, Z <: U] <: U +} + +final object Zero extends Nat { + type Fold[U, F[_ <: U] <: U, Z <: U] = Z +} + +final class Succ[N <: Nat] extends Nat { + type Fold[U, F[_ <: U] <: U, Z <: U] = F[N#Fold[U, F, Z]] +} + +trait HList { + type Head + type Tail <: HList + type Drop[N <: Nat] = N#Fold[HList, ({ type L[X <: HList] = X#Tail })#L, this.type] + type Apply[N <: Nat] = Drop[N]#Head +} + +class #: [H, T <: HList] extends HList { type Head = H; type Tail = T } + +object HNil extends HList { type Head = Nothing; type Tail = Nothing } diff --git a/test/files/pos/t5294c.scala b/test/files/pos/t5294c.scala new file mode 100644 index 0000000000..2709098988 --- /dev/null +++ b/test/files/pos/t5294c.scala @@ -0,0 +1,30 @@ +sealed trait Nat { + type IsZero[U, A <: U, B <: U] <: U +} + +final object Zero extends Nat { + type IsZero[U, T <: U, F <: U] = T +} + +final class Succ[N <: Nat] extends Nat { + type IsZero[U, T <: U, F <: U] = F +} + +trait HList { + type Head + type Tail <: HList + type Drop[N <: Nat] = N#IsZero[HList, this.type, Tail] + type Apply[N <: Nat] = Drop[N]#Head // typechecks as HList.this.Head +} + +object Test { + type ::[H, T <: HList] = HList { type Head = H; type Tail = T} + type HNil <: HList + type T = Int :: String :: HNil + + type U = T#Drop[Succ[Zero.type]]#Head + type V = T#Apply[Succ[Zero.type]] + var u: U = ??? + var v: V = ??? + u = v +} diff --git a/test/files/pos/t6161b.scala b/test/files/pos/t6161b.scala new file mode 100644 index 0000000000..5783cc85f2 --- /dev/null +++ b/test/files/pos/t6161b.scala @@ -0,0 +1,22 @@ +object t6161 { + trait N { + type Name + } + + trait N1 extends N { + class Name { + type ThisNameType <: Name + def encode: ThisNameType = ??? + } + } + + trait S { + self: N => // change to N1 and it compiles + type NameType <: Name + } + + object g extends S with N1 + + val n1: g.NameType = ??? + val n2: g.Name = n1.encode +} diff --git a/test/files/run/t5294.scala b/test/files/run/t5294.scala new file mode 100644 index 0000000000..2551ae89a6 --- /dev/null +++ b/test/files/run/t5294.scala @@ -0,0 +1,22 @@ +import scala.language.higherKinds + +package p { + trait T[+A, +CC] { + def t: CC + } + class C { + def test[CC[X] <: T[X,String] with T[X,Int]](from: CC[_]): Unit = () + } +} + +object Test { + def main(args: Array[String]): Unit = { + val symtab = reflect.runtime.universe.asInstanceOf[reflect.internal.SymbolTable] + val CTpe = reflect.runtime.universe.typeOf[p.C].asInstanceOf[symtab.Type] + val TClass = reflect.runtime.universe.symbolOf[p.T[_, _]].asInstanceOf[symtab.Symbol] + import symtab._ + val from = CTpe.member(TermName("test")).paramss.head.head + assert(from.baseClasses contains TClass) + assert(from.info.baseTypeIndex(TClass) != -1) // was failing! + } +} diff --git a/test/pending/pos/t6161.scala b/test/pending/pos/t6161.scala deleted file mode 100644 index 5783cc85f2..0000000000 --- a/test/pending/pos/t6161.scala +++ /dev/null @@ -1,22 +0,0 @@ -object t6161 { - trait N { - type Name - } - - trait N1 extends N { - class Name { - type ThisNameType <: Name - def encode: ThisNameType = ??? - } - } - - trait S { - self: N => // change to N1 and it compiles - type NameType <: Name - } - - object g extends S with N1 - - val n1: g.NameType = ??? - val n2: g.Name = n1.encode -} -- cgit v1.2.3 From 10aa78db5961284e0f4fb2fcb5a1f3bd7c357385 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Thu, 11 Aug 2016 17:17:38 +1000 Subject: Address review comments - clarify the intent of tests - Consolidate stripExistentialsAndTypeVars with similar logic in mergePrefixAndArgs - Refactor special cases in maybeRewrap The name isn't great, but I'm struggling to come up with a pithy way to describe the rogue band of types. --- src/reflect/scala/reflect/internal/Types.scala | 63 +++++++++++++++------- .../scala/reflect/internal/tpe/GlbLubs.scala | 18 ------- test/files/neg/lub-from-hell-2.scala | 7 +++ test/files/pos/lub-from-hell.scala | 9 +--- 4 files changed, 52 insertions(+), 45 deletions(-) (limited to 'test/files') diff --git a/src/reflect/scala/reflect/internal/Types.scala b/src/reflect/scala/reflect/internal/Types.scala index 523cb968e7..34949a2bb8 100644 --- a/src/reflect/scala/reflect/internal/Types.scala +++ b/src/reflect/scala/reflect/internal/Types.scala @@ -172,13 +172,16 @@ trait Types trait RewrappingTypeProxy extends SimpleTypeProxy { protected def maybeRewrap(newtp: Type) = ( if (newtp eq underlying) this - // BoundedWildcardTypes reach here during erroneous compilation: neg/t6258 - // Higher-kinded exclusion is because [x]CC[x] compares =:= to CC: pos/t3800 - // Avoid reusing the existing Wrapped(RefinedType) when we've be asked to wrap an =:= RefinementTypeRef, the - // distinction is important in base type sequences. - // Otherwise, if newtp =:= underlying, don't rewrap it. - else if (!newtp.isWildcard && !newtp.isHigherKinded && !newtp.isInstanceOf[RefinementTypeRef] && (newtp =:= underlying)) this - else rewrap(newtp) + else { + // - BoundedWildcardTypes reach here during erroneous compilation: neg/t6258 + // - Higher-kinded exclusion is because [x]CC[x] compares =:= to CC: pos/t3800 + // - Avoid reusing the existing Wrapped(RefinedType) when we've be asked to wrap an =:= RefinementTypeRef, the + // distinction is important in base type sequences. See TypesTest.testExistentialRefinement + // - Otherwise, if newtp =:= underlying, don't rewrap it. + val hasSpecialMeaningBeyond_=:= = newtp.isWildcard || newtp.isHigherKinded || newtp.isInstanceOf[RefinementTypeRef] + if (!hasSpecialMeaningBeyond_=:= && (newtp =:= underlying)) this + else rewrap(newtp) + } ) protected def rewrap(newtp: Type): Type @@ -1596,7 +1599,6 @@ trait Types (parents forall typeIsHigherKinded) && !phase.erasedTypes ) - override def typeParams = if (isHigherKinded) firstParent.typeParams else super.typeParams @@ -4415,6 +4417,37 @@ trait Types finally foreach2(tvs, saved)(_.suspended = _) } + final def stripExistentialsAndTypeVars(ts: List[Type], expandLazyBaseType: Boolean = false): (List[Type], List[Symbol]) = { + val needsStripping = ts.exists { + case _: RefinedType | _: TypeVar | _: ExistentialType => true + case _ => false + } + if (!needsStripping) (ts, Nil) // fast path for common case + else { + val tparams = mutable.ListBuffer[Symbol]() + val stripped = mutable.ListBuffer[Type]() + def stripType(tp: Type): Unit = tp match { + case rt: RefinedType if isIntersectionTypeForLazyBaseType(rt) => + if (expandLazyBaseType) + rt.parents foreach stripType + else { + devWarning(s"Unexpected RefinedType in stripExistentialsAndTypeVars $ts, not expanding") + stripped += tp + } + case ExistentialType(qs, underlying) => + tparams ++= qs + stripType(underlying) + case tv@TypeVar(_, constr) => + if (tv.instValid) stripType(constr.inst) + else if (tv.untouchable) stripped += tv + else abort("trying to do lub/glb of typevar " + tv) + case tp => stripped += tp + } + ts foreach stripType + (stripped.toList, tparams.toList) + } + } + /** Compute lub (if `variance == Covariant`) or glb (if `variance == Contravariant`) of given list * of types `tps`. All types in `tps` are typerefs or singletypes * with the same symbol. @@ -4422,17 +4455,7 @@ trait Types * Return `NoType` if the computation fails. */ def mergePrefixAndArgs(tps0: List[Type], variance: Variance, depth: Depth): Type = { - var tparams = mutable.ListBuffer[Symbol]() - val tps = tps0.flatMap { - case rt: RefinedType if isIntersectionTypeForLazyBaseType(rt) => rt.parents - case ExistentialType(qs, underlying) => - tparams ++= qs - underlying match { - case rt: RefinedType if isIntersectionTypeForLazyBaseType(rt) => rt.parents - case tp => tp :: Nil - } - case tp => tp :: Nil - } + val (tps, tparams) = stripExistentialsAndTypeVars(tps0, expandLazyBaseType = true) val merged = tps match { case tp :: Nil => tp @@ -4507,7 +4530,7 @@ trait Types case _ => abort(s"mergePrefixAndArgs($tps, $variance, $depth): unsupported tps") } - existentialAbstraction(tparams.toList, merged) + existentialAbstraction(tparams, merged) } def addMember(thistp: Type, tp: Type, sym: Symbol): Unit = addMember(thistp, tp, sym, AnyDepth) diff --git a/src/reflect/scala/reflect/internal/tpe/GlbLubs.scala b/src/reflect/scala/reflect/internal/tpe/GlbLubs.scala index c997dd30eb..108ce45cca 100644 --- a/src/reflect/scala/reflect/internal/tpe/GlbLubs.scala +++ b/src/reflect/scala/reflect/internal/tpe/GlbLubs.scala @@ -210,24 +210,6 @@ private[internal] trait GlbLubs { } } - private def stripExistentialsAndTypeVars(ts: List[Type]): (List[Type], List[Symbol]) = { - val quantified = ts flatMap { - case ExistentialType(qs, _) => qs - case t => List() - } - def stripType(tp: Type): Type = tp match { - case ExistentialType(_, res) => - res - case tv@TypeVar(_, constr) => - if (tv.instValid) stripType(constr.inst) - else if (tv.untouchable) tv - else abort("trying to do lub/glb of typevar "+tp) - case t => t - } - val strippedTypes = ts mapConserve stripType - (strippedTypes, quantified) - } - /** Does this set of types have the same weak lub as * it does regular lub? This is exposed so lub callers * can discover whether the trees they are typing will diff --git a/test/files/neg/lub-from-hell-2.scala b/test/files/neg/lub-from-hell-2.scala index 96760c6edf..18c99dfada 100644 --- a/test/files/neg/lub-from-hell-2.scala +++ b/test/files/neg/lub-from-hell-2.scala @@ -4,3 +4,10 @@ class Test { def bar(a: Boolean, b: scala.collection.mutable.SetLike[Any,scala.collection.mutable.Set[Any]], c: scala.collection.mutable.Buffer[Any]) = if (a) b else c // bar produces an ill-bounded LUB in 2.11.8. After this commit, which fixes a bug in existential+refinement lubs, foo also fails. } +// This test case minimizes a case that stated to fail compile after my fixes in SI-5294. +// `foo` used to compile for the wrong reason, `mergePrefixAndArgs` failed to transpose a +// ragged matrix and skipped to the next level of the base type sequences to find a common type symbol. +// +// My changes fixed the root cause of the ragged matrix, which uncovered the latent bug. +// For comparison, `bar` failed to compile before _and_ after my changes for the same reason: +// f-bounded types involved in LUBs can sometimes produce an ill-bounded LUB. diff --git a/test/files/pos/lub-from-hell.scala b/test/files/pos/lub-from-hell.scala index a7d2a99b08..cb4b1733c7 100644 --- a/test/files/pos/lub-from-hell.scala +++ b/test/files/pos/lub-from-hell.scala @@ -1,11 +1,6 @@ class Test { trait Tree def foo(b: Boolean, buf: collection.mutable.ArrayBuffer[Any], acc: StringBuilder) = if (b) buf else acc - - // def bar(b: Boolean, - // buf: scala.collection.IndexedSeqLike[Any,Cloneable with Mutable with Equals], - // acc: scala.collection.IndexedSeqLike[_18,scala.collection.mutable.IndexedSeq[_18]] with scala.collection.IndexedSeqLike[_18,IndexedSeq[_18]] forSome { type _18 >: String with Char } - // ) = if (b) buf else acc - - } +// This test case minimizes a case that failed to compile due to a bug in my work on +// SI-5294. After refining my patches, it compiles again, as expected. \ No newline at end of file -- cgit v1.2.3