Skip to content

Fix #7877: Refine instantiation criterion before implicit search #7907

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jan 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions compiler/src/dotty/tools/dotc/core/TypeOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down Expand Up @@ -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
)

Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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? */
Expand Down Expand Up @@ -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,
Expand Down
83 changes: 50 additions & 33 deletions compiler/src/dotty/tools/dotc/typer/Inferencing.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down Expand Up @@ -78,27 +88,35 @@ 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)
typr.println(i"forced instantiation of ${tvar.origin} = $inst")
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 =>
Expand All @@ -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`:
Expand Down Expand Up @@ -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)
}

2 changes: 1 addition & 1 deletion library/src/scala/Eql.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down
21 changes: 21 additions & 0 deletions tests/pos/i7877.scala
Original file line number Diff line number Diff line change
@@ -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"))
}