From eac33f06cbc37327ded86dd51cae735f89f15519 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 7 Jul 2015 18:56:36 +0200 Subject: [PATCH 1/4] Make compareHK work if other side is already a lambda. There ws a missing case, where a lambda projection `T#Apply` is compared to something to a type lambda. In that case we should compare the prefix `T` directly wiht the type lambda. With that modification i553 works if the canBuildFrom implicit is imported. But the implicit is not yet found in the type scope. --- src/dotty/tools/dotc/core/TypeComparer.scala | 19 +++++++++++++------ tests/pos/i553-shuffle.scala | 8 ++++++++ 2 files changed, 21 insertions(+), 6 deletions(-) create mode 100644 tests/pos/i553-shuffle.scala diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index ea815f6c0778..b4b1573286b6 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -467,16 +467,23 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { } /** If `projection` is of the form T # Apply where `T` is an instance of a Lambda class, - * and `other` is not a type lambda projection, then convert `other` to a type lambda `U`, and - * continue with `T <:< U` if `inOrder` is true and `U <:< T` otherwise. + * then + * (1) if `other` is not a (possibly projected) type lambda, convert `other` to a type lambda `U`, + * and continue with `T <:< U` if `inOrder` is true and `U <:< T` otherwise. + * (2) if `other` is a already a type lambda, + * continue with `T <:< other` if `inOrder` is true and `other <:< T` otherwise. */ def compareHK(projection: NamedType, other: Type, inOrder: Boolean) = projection.name == tpnme.Apply && { val lambda = projection.prefix.LambdaClass(forcing = true) - lambda.exists && !other.isLambda && - other.testLifted(lambda.typeParams, - if (inOrder) isSubType(projection.prefix, _) else isSubType(_, projection.prefix), - if (inOrder) Nil else classBounds(projection.prefix)) + lambda.exists && { + if (!other.isLambda) + other.testLifted(lambda.typeParams, + if (inOrder) isSubType(projection.prefix, _) else isSubType(_, projection.prefix), + if (inOrder) Nil else classBounds(projection.prefix)) + else if (inOrder) isSubType(projection.prefix, other) + else isSubType(other, projection.prefix) + } } /** The class symbols bounding the type of the `Apply` member of `tp` */ diff --git a/tests/pos/i553-shuffle.scala b/tests/pos/i553-shuffle.scala new file mode 100644 index 000000000000..105379918e88 --- /dev/null +++ b/tests/pos/i553-shuffle.scala @@ -0,0 +1,8 @@ +import scala.util.Random +import scala.collection.immutable.List._ +object Test { + def test = { + val rand = new Random + rand.shuffle(List(1,2))//(List.canBuildFrom[Int]) // fails + } +} From ec2d7d675cdec3bc4b9a1e276971430f00cd2800 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 7 Jul 2015 21:28:03 +0200 Subject: [PATCH 2/4] Refine lookupRefined for Wildcard prefixes lookupRefined used to fall back to unbounded Wildcard, given a Wildcard prefix. But that loses bounds when called from wildApprox, which in turn loses companion objects in the implicit scope of a type. In `i553-shuffle.scala` this happened because we projected with #Apply on a type that approximated to a bounded wildcard. The fix keeps the bounds, projecting in turn on them. --- src/dotty/tools/dotc/core/Types.scala | 8 +++++++- tests/pos/i553-shuffle.scala | 3 +-- tests/{pending/run => pos}/vector1.scala | 0 3 files changed, 8 insertions(+), 3 deletions(-) rename tests/{pending/run => pos}/vector1.scala (100%) diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index d6bb9c3c574e..c651e34d8b0b 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -869,7 +869,13 @@ object Types { case SkolemType(tp) => tp.lookupRefined(name) case pre: WildcardType => - WildcardType + pre.optBounds match { + case TypeBounds(lo, hi) if name.isTypeName => + // for term names, forming a Termref with just the name risks losing overloaded instance + WildcardType(TypeBounds(NamedType(lo, name), NamedType(hi, name))) + case _ => + WildcardType + } case pre: TypeRef => pre.info match { case TypeAlias(alias) => loop(alias, resolved) diff --git a/tests/pos/i553-shuffle.scala b/tests/pos/i553-shuffle.scala index 105379918e88..c1022fb0b4b4 100644 --- a/tests/pos/i553-shuffle.scala +++ b/tests/pos/i553-shuffle.scala @@ -1,8 +1,7 @@ import scala.util.Random -import scala.collection.immutable.List._ object Test { def test = { val rand = new Random - rand.shuffle(List(1,2))//(List.canBuildFrom[Int]) // fails + rand.shuffle(List(1,2))// infers implicit argument list (List.canBuildFrom[Int]) } } diff --git a/tests/pending/run/vector1.scala b/tests/pos/vector1.scala similarity index 100% rename from tests/pending/run/vector1.scala rename to tests/pos/vector1.scala From 87cfcf92a4772ecda083b9a25734bfcfb7423725 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 8 Jul 2015 17:20:20 +0200 Subject: [PATCH 3/4] Revert "Refine lookupRefined for Wildcard prefixes" This reverts commit ec2d7d675cdec3bc4b9a1e276971430f00cd2800. --- src/dotty/tools/dotc/core/Types.scala | 8 +------- tests/{pos => pending/run}/vector1.scala | 0 tests/pos/i553-shuffle.scala | 3 ++- 3 files changed, 3 insertions(+), 8 deletions(-) rename tests/{pos => pending/run}/vector1.scala (100%) diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index c651e34d8b0b..d6bb9c3c574e 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -869,13 +869,7 @@ object Types { case SkolemType(tp) => tp.lookupRefined(name) case pre: WildcardType => - pre.optBounds match { - case TypeBounds(lo, hi) if name.isTypeName => - // for term names, forming a Termref with just the name risks losing overloaded instance - WildcardType(TypeBounds(NamedType(lo, name), NamedType(hi, name))) - case _ => - WildcardType - } + WildcardType case pre: TypeRef => pre.info match { case TypeAlias(alias) => loop(alias, resolved) diff --git a/tests/pos/vector1.scala b/tests/pending/run/vector1.scala similarity index 100% rename from tests/pos/vector1.scala rename to tests/pending/run/vector1.scala diff --git a/tests/pos/i553-shuffle.scala b/tests/pos/i553-shuffle.scala index c1022fb0b4b4..105379918e88 100644 --- a/tests/pos/i553-shuffle.scala +++ b/tests/pos/i553-shuffle.scala @@ -1,7 +1,8 @@ import scala.util.Random +import scala.collection.immutable.List._ object Test { def test = { val rand = new Random - rand.shuffle(List(1,2))// infers implicit argument list (List.canBuildFrom[Int]) + rand.shuffle(List(1,2))//(List.canBuildFrom[Int]) // fails } } From 30aca0f1e1345161ea2d75b9722a76c290b145be Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 8 Jul 2015 17:26:07 +0200 Subject: [PATCH 4/4] Another solution to the problem of discarded types in wildApprox lookupRefined on a Wildcard prefix will return an unbounded Wildcard type. This can destroy information that's important for determining the companions making up the implicit scope of a type. We handle this case by maintaining a collection for types that were discarded in that way and taking these types intoa ccount separately when computing companions. Examples where this is necessary include `i553-shuffle.scala` abd `vector1.scala`. --- src/dotty/tools/dotc/typer/Implicits.scala | 19 ++++++++-- src/dotty/tools/dotc/typer/ProtoTypes.scala | 39 ++++++++++++++------- 2 files changed, 42 insertions(+), 16 deletions(-) diff --git a/src/dotty/tools/dotc/typer/Implicits.scala b/src/dotty/tools/dotc/typer/Implicits.scala index a3ddca5d99db..c5755e3fa7d8 100644 --- a/src/dotty/tools/dotc/typer/Implicits.scala +++ b/src/dotty/tools/dotc/typer/Implicits.scala @@ -462,7 +462,8 @@ trait Implicits { self: Typer => } /** The expected type where parameters and uninstantiated typevars are replaced by wildcard types */ - val wildProto = implicitProto(pt, wildApprox(_)) + val approximator = new WildApproxMap(new mutable.ListBuffer[Type]) + val wildProto = implicitProto(pt, wildApprox(_, approximator)) /** Search failures; overridden in ExplainedImplicitSearch */ protected def nonMatchingImplicit(ref: TermRef): SearchFailure = NoImplicitMatches @@ -565,11 +566,23 @@ trait Implicits { self: Typer => case result: SearchSuccess => result case result: AmbiguousImplicits => result case result: SearchFailure => - searchImplicits(implicitScope(wildProto).eligible, contextual = false) + searchImplicits(implicitScope(wildProto, approximator.discarded).eligible, contextual = false) } } - def implicitScope(tp: Type): OfTypeImplicits = ctx.runInfo.implicitScope(tp, ctx) + /** The implicit scope corresponding to type `tp` and discarded parts `others`. + */ + def implicitScope(tp: Type, others: Traversable[Type]): OfTypeImplicits = { + val typeScope = ctx.runInfo.implicitScope(tp, ctx) + if (others.isEmpty) typeScope + else { + val allCompanionRefs = new TermRefSet + allCompanionRefs ++= typeScope.companionRefs + for (tp <- others) + allCompanionRefs ++= ctx.runInfo.implicitScope(tp, ctx).companionRefs + new OfTypeImplicits(tp, allCompanionRefs)(ctx) + } + } } final class ExplainedImplicitSearch(pt: Type, argument: Tree, pos: Position)(implicit ctx: Context) diff --git a/src/dotty/tools/dotc/typer/ProtoTypes.scala b/src/dotty/tools/dotc/typer/ProtoTypes.scala index 9a012c30e0b0..622fbc6167ac 100644 --- a/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -378,24 +378,31 @@ object ProtoTypes { final def wildApprox(tp: Type, theMap: WildApproxMap = null)(implicit ctx: Context): Type = tp match { case tp: NamedType => // default case, inlined for speed if (tp.symbol.isStatic) tp - else tp.derivedSelect(wildApprox(tp.prefix, theMap)) + else { + val pre = wildApprox(tp.prefix, theMap) + val res = tp.derivedSelect(pre) + if ((res eq WildcardType) && (pre ne WildcardType) && + (theMap != null) && (theMap.discarded != null)) + theMap.discarded += pre + res + } case tp: RefinedType => // default case, inlined for speed tp.derivedRefinedType(wildApprox(tp.parent, theMap), tp.refinedName, wildApprox(tp.refinedInfo, theMap)) case tp: TypeAlias => // default case, inlined for speed tp.derivedTypeAlias(wildApprox(tp.alias, theMap)) case tp @ PolyParam(poly, pnum) => ctx.typerState.constraint.entry(tp) match { - case bounds: TypeBounds => wildApprox(WildcardType(bounds)) - case NoType => WildcardType(wildApprox(poly.paramBounds(pnum)).bounds) - case inst => wildApprox(inst) + case bounds: TypeBounds => wildApprox(WildcardType(bounds), theMap) + case NoType => WildcardType(wildApprox(poly.paramBounds(pnum), theMap).bounds) + case inst => wildApprox(inst, theMap) } case MethodParam(mt, pnum) => - WildcardType(TypeBounds.upper(wildApprox(mt.paramTypes(pnum)))) + WildcardType(TypeBounds.upper(wildApprox(mt.paramTypes(pnum), theMap))) case tp: TypeVar => - wildApprox(tp.underlying) + wildApprox(tp.underlying, theMap) case tp: AndType => - val tp1a = wildApprox(tp.tp1) - val tp2a = wildApprox(tp.tp2) + val tp1a = wildApprox(tp.tp1, theMap) + val tp2a = wildApprox(tp.tp2, theMap) def wildBounds(tp: Type) = if (tp.isInstanceOf[WildcardType]) tp.bounds else TypeBounds.upper(tp) if (tp1a.isInstanceOf[WildcardType] || tp2a.isInstanceOf[WildcardType]) @@ -403,23 +410,29 @@ object ProtoTypes { else tp.derivedAndType(tp1a, tp2a) case tp: OrType => - val tp1a = wildApprox(tp.tp1) - val tp2a = wildApprox(tp.tp2) + val tp1a = wildApprox(tp.tp1, theMap) + val tp2a = wildApprox(tp.tp2, theMap) if (tp1a.isInstanceOf[WildcardType] || tp2a.isInstanceOf[WildcardType]) WildcardType(tp1a.bounds | tp2a.bounds) else tp.derivedOrType(tp1a, tp2a) case tp: SelectionProto => - tp.derivedSelectionProto(tp.name, wildApprox(tp.memberProto), NoViewsAllowed) + tp.derivedSelectionProto(tp.name, wildApprox(tp.memberProto, theMap), NoViewsAllowed) case tp: ViewProto => - tp.derivedViewProto(wildApprox(tp.argType), wildApprox(tp.resultType)) + tp.derivedViewProto(wildApprox(tp.argType, theMap), wildApprox(tp.resultType, theMap)) case _: ThisType | _: BoundType | NoPrefix => // default case, inlined for speed tp case _ => (if (theMap != null) theMap else new WildApproxMap).mapOver(tp) } - private[ProtoTypes] class WildApproxMap(implicit ctx: Context) extends TypeMap { + /** A backing map for `wildApprox`. + * @param discarded A ListBuffer in which to store parts of intermediate steps of the approximation + * that have been discarded in a lookupRefined yielding a WildcardType. + * The idea is that these bits still matter as far as companion search is + * concerned, so they need to be kept somewhere. + */ + private[dotty] class WildApproxMap(val discarded: mutable.ListBuffer[Type] = null)(implicit ctx: Context) extends TypeMap { def apply(tp: Type) = wildApprox(tp, this) }