diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index 9ac5be0e8f39..e71416828505 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -555,17 +555,17 @@ trait TypeOps { this: Context => // TODO: Make standalone object. val childTp = if (child.isTerm) child.termRef else child.typeRef - instantiate(childTp, parent)(ctx.fresh.setNewTyperState()).dealias + instantiateToSubType(childTp, parent)(ctx.fresh.setNewTyperState()).dealias } /** Instantiate type `tp1` to be a subtype of `tp2` * - * Return the instantiated type if type parameters and this type + * Return the instantiated type if type parameters in this type * in `tp1` can be instantiated such that `tp1 <:< tp2`. * * Otherwise, return NoType. */ - private def instantiate(tp1: NamedType, tp2: Type)(implicit ctx: Context): Type = { + private def instantiateToSubType(tp1: NamedType, tp2: Type)(implicit ctx: Context): Type = { /** expose abstract type references to their bounds or tvars according to variance */ class AbstractTypeMap(maximize: Boolean)(implicit ctx: Context) extends TypeMap { def expose(lo: Type, hi: Type): Type = @@ -637,7 +637,6 @@ trait TypeOps { this: Context => // TODO: Make standalone object. tvar => !(ctx.typerState.constraint.entry(tvar.origin) `eq` tvar.origin.underlying) || (tvar `eq` removeThisType.prefixTVar), - minimizeAll = false, allowBottom = false ) diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 58456da5dcaf..cd02655c0059 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -779,7 +779,7 @@ trait Implicits { self: Typer => /** If `formal` is of the form Eql[T, U], try to synthesize an * `Eql.eqlAny[T, U]` as solution. */ - lazy val synthesizedEq: SpecialHandler = { + lazy val synthesizedEql: SpecialHandler = { (formal, span) => implicit ctx => { /** Is there an `Eql[T, T]` instance, assuming -strictEquality? */ @@ -1091,7 +1091,7 @@ trait Implicits { self: Typer => mySpecialHandlers = List( defn.ClassTagClass -> synthesizedClassTag, defn.QuotedTypeClass -> synthesizedTypeTag, - defn.EqlClass -> synthesizedEq, + defn.EqlClass -> synthesizedEql, defn.TupledFunctionClass -> synthesizedTupleFunction, defn.ValueOfClass -> synthesizedValueOf, defn.Mirror_ProductClass -> synthesizedProductMirror, diff --git a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index e587eb8e2fff..0a215887cc06 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -43,10 +43,20 @@ object Inferencing { if (isFullyDefined(tp, ForceDegree.all)) tp else throw new Error(i"internal error: type of $what $tp is not fully defined, pos = $span") // !!! DEBUG - /** Instantiate selected type variables `tvars` in type `tp` */ + /** Instantiate selected type variables `tvars` in type `tp` in a special mode: + * 1. If a type variable is constrained from below (i.e. constraint bound != given lower bound) + * it is minimized. + * 2. Otherwise, if the type variable is constrained from above, it is maximized. + * 3. Otherwise, if the type variable has a lower bound != Nothing, it is minimized. + * 4. Otherwise, if the type variable has an upper bound != Any, it is maximized. + * If none of (1) - (4) applies, the type variable is left uninstantiated. + * The method is called to instantiate type variables before an implicit search. + */ def instantiateSelected(tp: Type, tvars: List[Type])(implicit ctx: Context): Unit = if (tvars.nonEmpty) - new IsFullyDefinedAccumulator(new ForceDegree.Value(tvars.contains, minimizeAll = true, allowBottom = false)).process(tp) + IsFullyDefinedAccumulator( + ForceDegree.Value(tvars.contains, allowBottom = false), minimizeSelected = true + ).process(tp) /** Instantiate any type variables in `tp` whose bounds contain a reference to * one of the parameters in `tparams` or `vparamss`. @@ -78,19 +88,27 @@ object Inferencing { * 2. T is maximized if the constraint over T is only from above (i.e. * constrained upper bound != given upper bound and * constrained lower bound == given lower bound). - * If (1) and (2) do not apply: - * 3. T is minimized if forceDegree is minimizeAll. - * 4. Otherwise, T is maximized if it appears only contravariantly in the given type, - * or if forceDegree is `noBottom` and T's minimized value is a bottom type. - * 5. Otherwise, T is minimized. * - * The instantiation is done in two phases: + * If (1) and (2) do not apply, and minimizeSelected is set: + * 3. T is minimized if it has a lower bound (different from Nothing) in the + * current constraint (the bound might come from T's declaration). + * 4. Otherwise, T is maximized if it has an upper bound (different from Any) + * in the currented constraint (the bound might come from T's declaration). + * 5. Otherwise, T is not instantiated at all. + + * If (1) and (2) do not apply, and minimizeSelected is not set: + * 6: T is maximized if it appears only contravariantly in the given type, + * or if forceDegree is `noBottom` and T has no lower bound different from Nothing. + * 7. Otherwise, T is minimized. + * + * The instantiation for (6) and (7) is done in two phases: * 1st Phase: Try to instantiate minimizable type variables to * their lower bound. Record whether successful. * 2nd Phase: If first phase was successful, instantiate all remaining type variables * to their upper bound. */ - private class IsFullyDefinedAccumulator(force: ForceDegree.Value)(implicit ctx: Context) extends TypeAccumulator[Boolean] { + private class IsFullyDefinedAccumulator(force: ForceDegree.Value, minimizeSelected: Boolean = false) + (implicit ctx: Context) extends TypeAccumulator[Boolean] { private def instantiate(tvar: TypeVar, fromBelow: Boolean): Type = { val inst = tvar.instantiate(fromBelow) @@ -98,7 +116,7 @@ object Inferencing { inst } - private var toMaximize: Boolean = false + private var toMaximize: List[TypeVar] = Nil def apply(x: Boolean, tp: Type): Boolean = tp.dealias match { case _: WildcardType | _: ProtoType => @@ -108,34 +126,33 @@ object Inferencing { && ctx.typerState.constraint.contains(tvar) && { val direction = instDirection(tvar.origin) - def preferMin = - force.minimizeAll && (tvar.hasLowerBound || !tvar.hasUpperBound) - || variance >= 0 && (force.allowBottom || tvar.hasLowerBound) - if (direction != 0) instantiate(tvar, direction < 0) - else if (preferMin) instantiate(tvar, fromBelow = true) - else toMaximize = true + if direction != 0 then + instantiate(tvar, fromBelow = direction < 0) + else if minimizeSelected then + if tvar.hasLowerBound then instantiate(tvar, fromBelow = true) + else if tvar.hasUpperBound then instantiate(tvar, fromBelow = false) + else () // hold off instantiating unbounded unconstrained variables + else if variance >= 0 && (force.allowBottom || tvar.hasLowerBound) then + instantiate(tvar, fromBelow = true) + else + toMaximize = tvar :: toMaximize foldOver(x, tvar) } case tp => foldOver(x, tp) } - private class UpperInstantiator(implicit ctx: Context) extends TypeAccumulator[Unit] { - def apply(x: Unit, tp: Type): Unit = { - tp match { - case tvar: TypeVar if !tvar.isInstantiated => + def process(tp: Type): Boolean = + // Maximize type vars in the order they were visited before */ + def maximize(tvars: List[TypeVar]): Unit = tvars match + case tvar :: tvars1 => + maximize(tvars1) + if !tvar.isInstantiated then instantiate(tvar, fromBelow = false) - case _ => - } - foldOver(x, tp) - } - } - - def process(tp: Type): Boolean = { + case nil => val res = apply(true, tp) - if (res && toMaximize) new UpperInstantiator().apply((), tp) + if res then maximize(toMaximize) res - } } /** For all type parameters occurring in `tp`: @@ -492,9 +509,9 @@ trait Inferencing { this: Typer => /** An enumeration controlling the degree of forcing in "is-dully-defined" checks. */ @sharable object ForceDegree { - class Value(val appliesTo: TypeVar => Boolean, val minimizeAll: Boolean, val allowBottom: Boolean = true) - val none: Value = new Value(_ => false, minimizeAll = false) - val all: Value = new Value(_ => true, minimizeAll = false) - val noBottom: Value = new Value(_ => true, minimizeAll = false, allowBottom = false) + class Value(val appliesTo: TypeVar => Boolean, val allowBottom: Boolean) + val none: Value = new Value(_ => false, allowBottom = true) + val all: Value = new Value(_ => true, allowBottom = true) + val noBottom: Value = new Value(_ => true, allowBottom = false) } diff --git a/library/src/scala/Eql.scala b/library/src/scala/Eql.scala index 341a291f6d77..36f2e7b764e6 100644 --- a/library/src/scala/Eql.scala +++ b/library/src/scala/Eql.scala @@ -9,7 +9,7 @@ sealed trait Eql[-L, -R] /** Companion object containing a few universally known `Eql` instances. * Eql instances involving primitive types or the Null type are handled directly in - * the compiler (see Implicits.synthesizedEq), so they are not included here. + * the compiler (see Implicits.synthesizedEql), so they are not included here. */ object Eql { /** A universal `Eql` instance. */ diff --git a/tests/pos/i7877.scala b/tests/pos/i7877.scala new file mode 100644 index 000000000000..5a056636dd60 --- /dev/null +++ b/tests/pos/i7877.scala @@ -0,0 +1,21 @@ +object Example extends App { + + trait ZSink[-R, +E, +A0, -A, +B] { + def orElse[R1 <: R, E1, A00 >: A0, A1 <: A, C]( + that: ZSink[R1, E1, A00, A1, C] + )(implicit ev: A1 =:= A00): ZSink[R1, E1, A00, A1, Either[B, C]] = + ??? + } + + object ZSink { + def identity[A]: ZSink[Any, Unit, Nothing, A, A] = ??? + def fail[E](e: E): ZSink[Any, E, Nothing, Any, Nothing] = ??? + } + + // compiles + val a: ZSink[Any, String, Int, Int, Either[Int, Nothing]] = + ZSink.identity[Int].orElse(ZSink.fail("Ouch")) + + // cannot prove that Int =:= Nothing + ZSink.identity[Int].orElse(ZSink.fail("Ouch")) +} \ No newline at end of file