Skip to content

Commit

Permalink
Infer: Don't minimise to Nothing if there's an upper bound (#16786)
Browse files Browse the repository at this point in the history
  • Loading branch information
dwijnand committed Jan 30, 2023
2 parents b8ad7b1 + 12a8051 commit 75ade8f
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 3 deletions.
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4781,7 +4781,7 @@ object Types {
def hasLowerBound(using Context): Boolean = !currentEntry.loBound.isExactlyNothing

/** For uninstantiated type variables: Is the upper bound different from Any? */
def hasUpperBound(using Context): Boolean = !currentEntry.hiBound.isRef(defn.AnyClass)
def hasUpperBound(using Context): Boolean = !currentEntry.hiBound.finalResultType.isExactlyAny

/** Unwrap to instance (if instantiated) or origin (if not), until result
* is no longer a TypeVar
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/Inferencing.scala
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ object Inferencing {
// else hold off instantiating unbounded unconstrained variable
else if direction != 0 then
instantiate(tvar, fromBelow = direction < 0)
else if variance >= 0 && (force.ifBottom == IfBottom.ok || tvar.hasLowerBound) then
else if variance >= 0 && (force.ifBottom == IfBottom.ok && !tvar.hasUpperBound || tvar.hasLowerBound) then
instantiate(tvar, fromBelow = true)
else if variance >= 0 && force.ifBottom == IfBottom.fail then
return false
Expand Down
57 changes: 57 additions & 0 deletions compiler/test/dotty/tools/dotc/typer/InstantiateModel.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package dotty.tools
package dotc
package typer

// Modelling the decision in IsFullyDefined
object InstantiateModel:
enum LB { case NN; case LL; case L1 }; import LB.*
enum UB { case AA; case UU; case U1 }; import UB.*
enum Var { case V; case NotV }; import Var.*
enum MSe { case M; case NotM }; import MSe.*
enum Bot { case Fail; case Ok; case Flip }; import Bot.*
enum Act { case Min; case Max; case ToMax; case Skip; case False }; import Act.*

// NN/AA = Nothing/Any
// LL/UU = the original bounds, on the type parameter
// L1/U1 = the constrained bounds, on the type variable
// V = variance >= 0 ("non-contravariant")
// MSe = minimisedSelected
// Bot = IfBottom
// ToMax = delayed maximisation, via addition to toMaximize
// Skip = minimisedSelected "hold off instantiating"
// False = return false

// there are 9 combinations:
// # | LB | UB | d | // d = direction
// --+----+----+---+
// 1 | L1 | AA | - | L1 <: T
// 2 | L1 | UU | - | L1 <: T <: UU
// 3 | LL | U1 | + | LL <: T <: U1
// 4 | NN | U1 | + | T <: U1
// 5 | L1 | U1 | 0 | L1 <: T <: U1
// 6 | LL | UU | 0 | LL <: T <: UU
// 7 | LL | AA | 0 | LL <: T
// 8 | NN | UU | 0 | T <: UU
// 9 | NN | AA | 0 | T

def decide(lb: LB, ub: UB, v: Var, bot: Bot, m: MSe): Act = (lb, ub) match
case (L1, AA) => Min
case (L1, UU) => Min
case (LL, U1) => Max
case (NN, U1) => Max

case (L1, U1) => if m==M || v==V then Min else ToMax
case (LL, UU) => if m==M || v==V then Min else ToMax
case (LL, AA) => if m==M || v==V then Min else ToMax

case (NN, UU) => bot match
case _ if m==M => Max
//case Ok if v==V => Min // removed, i14218 fix
case Fail if v==V => False
case _ => ToMax

case (NN, AA) => bot match
case _ if m==M => Skip
case Ok if v==V => Min
case Fail if v==V => False
case _ => ToMax
2 changes: 1 addition & 1 deletion tests/neg/i15525.scala
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def element22(
transmittable20.Type / transmittable21.Type
} = ???

def test22 =
def test22 = // error
Resolution(
element22(
Resolution(element0), Resolution(element0), // error // error
Expand Down
22 changes: 22 additions & 0 deletions tests/pos/i14218.http4s.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// A minimisation from http4s,
// which broke while implementing the fix for i14218.

final class Bar[+F[_]]
object Bar:
def empty[F[_]]: Bar[F] = new Bar[Nothing]

final class Foo[+F[_]]

object Foo:
def apply[F[_]](bar: Bar[F] = Bar.empty): Foo[F] = new Foo

class Test:
def test[F[_]]: Foo[F] = Foo[F]()

//-- [E007] Type Mismatch Error
//12 | def test[F[_]]: Foo[F] = Foo[F]()
// | ^^^^^^
// | Found: Bar[[_] =>> Any]
// | Required: Bar[F]
// |
// | where: F is a type in method t1 with bounds <: [_] =>> Any
15 changes: 15 additions & 0 deletions tests/pos/i14218.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class Pet
class Cat extends Pet

class Z1[ S1 <: Pet](val fn: S1 => Unit)
class Z2[ S2 ](val fn: S2 => Unit)
class Z3[-S3 <: Pet](val fn: S3 => Unit)

abstract class Test:
def test =
val r1 = new Z1((_: Pet) => ()); eat[Z1[Pet]](r1) // the case: using the parameter bound in situ infers Z[Nothing]
val r2 = new Z2((_: Pet) => ()); eat[Z2[Pet]](r2) // counter-example: infers as desired without an upper bound
val r3 = new Z3((_: Pet) => ()); eat[Z3[Pet]](r3) // workaround: declare it contravariant
val r4 = new Z1((_: Cat) => ()); eat[Z1[Cat]](r4) // counter-example: infers as desired with a subtype

def eat[T](x: T): Unit

0 comments on commit 75ade8f

Please sign in to comment.