From b87f9e51fe96888b26cab022a07a9295e4ea1dac Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 6 Jan 2020 21:11:22 +0100 Subject: [PATCH 1/4] Rename synthesizedEq -> synthesizedEql --- compiler/src/dotty/tools/dotc/typer/Implicits.scala | 4 ++-- library/src/scala/Eql.scala | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) 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/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. */ From e81d0a8916d6e4670420f647d870f4b4afbf4470 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 6 Jan 2020 21:28:43 +0100 Subject: [PATCH 2/4] Streamline IsFullyDefinedAccumulator Instead of doing two traversals, keep type variables that need maximization in a list. --- .../dotty/tools/dotc/typer/Inferencing.scala | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index e587eb8e2fff..329e57841f28 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -98,7 +98,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 => @@ -113,29 +113,24 @@ object Inferencing { || variance >= 0 && (force.allowBottom || tvar.hasLowerBound) if (direction != 0) instantiate(tvar, direction < 0) else if (preferMin) instantiate(tvar, fromBelow = true) - else toMaximize = 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`: From c252ffd1f12ce4ca031c511878503c48aeb4005c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 6 Jan 2020 22:03:10 +0100 Subject: [PATCH 3/4] Fix #7877: Refine instantiation criterion before implicit search Refine criterion when to instantiate before implicit search. Don' instantiate typevars that are completely unconstrained. Rely instead on resolving these typevars by the implicit search. --- .../dotty/tools/dotc/typer/Inferencing.scala | 4 +++- tests/pos/i7877.scala | 21 +++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 tests/pos/i7877.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index 329e57841f28..93b1f064b4c8 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -112,7 +112,9 @@ object Inferencing { 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 if (preferMin) + if force.minimizeAll && !tvar.hasLowerBound then () // do nothing + else instantiate(tvar, fromBelow = true) else toMaximize = tvar :: toMaximize foldOver(x, tvar) } 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 From 087b8d4e94c42d74ef8d632374b69efd0e456114 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 7 Jan 2020 12:13:18 +0100 Subject: [PATCH 4/4] Refactor IsFullyDefinedAccumulator for clarity --- .../src/dotty/tools/dotc/core/TypeOps.scala | 7 +-- .../dotty/tools/dotc/typer/Inferencing.scala | 62 ++++++++++++------- 2 files changed, 44 insertions(+), 25 deletions(-) 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/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index 93b1f064b4c8..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) @@ -108,14 +126,16 @@ 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) - if force.minimizeAll && !tvar.hasLowerBound then () // do nothing - else instantiate(tvar, fromBelow = true) - else toMaximize = tvar :: toMaximize + 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 => @@ -489,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) }