Skip to content

Commit 12a8051

Browse files
committed
Infer: Don't minimise to Nothing if there's an upper bound
1 parent c0a7d12 commit 12a8051

File tree

6 files changed

+97
-3
lines changed

6 files changed

+97
-3
lines changed

compiler/src/dotty/tools/dotc/core/Types.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -4781,7 +4781,7 @@ object Types {
47814781
def hasLowerBound(using Context): Boolean = !currentEntry.loBound.isExactlyNothing
47824782

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

47864786
/** Unwrap to instance (if instantiated) or origin (if not), until result
47874787
* is no longer a TypeVar

compiler/src/dotty/tools/dotc/typer/Inferencing.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ object Inferencing {
183183
// else hold off instantiating unbounded unconstrained variable
184184
else if direction != 0 then
185185
instantiate(tvar, fromBelow = direction < 0)
186-
else if variance >= 0 && (force.ifBottom == IfBottom.ok || tvar.hasLowerBound) then
186+
else if variance >= 0 && (force.ifBottom == IfBottom.ok && !tvar.hasUpperBound || tvar.hasLowerBound) then
187187
instantiate(tvar, fromBelow = true)
188188
else if variance >= 0 && force.ifBottom == IfBottom.fail then
189189
return false
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package dotty.tools
2+
package dotc
3+
package typer
4+
5+
// Modelling the decision in IsFullyDefined
6+
object InstantiateModel:
7+
enum LB { case NN; case LL; case L1 }; import LB.*
8+
enum UB { case AA; case UU; case U1 }; import UB.*
9+
enum Var { case V; case NotV }; import Var.*
10+
enum MSe { case M; case NotM }; import MSe.*
11+
enum Bot { case Fail; case Ok; case Flip }; import Bot.*
12+
enum Act { case Min; case Max; case ToMax; case Skip; case False }; import Act.*
13+
14+
// NN/AA = Nothing/Any
15+
// LL/UU = the original bounds, on the type parameter
16+
// L1/U1 = the constrained bounds, on the type variable
17+
// V = variance >= 0 ("non-contravariant")
18+
// MSe = minimisedSelected
19+
// Bot = IfBottom
20+
// ToMax = delayed maximisation, via addition to toMaximize
21+
// Skip = minimisedSelected "hold off instantiating"
22+
// False = return false
23+
24+
// there are 9 combinations:
25+
// # | LB | UB | d | // d = direction
26+
// --+----+----+---+
27+
// 1 | L1 | AA | - | L1 <: T
28+
// 2 | L1 | UU | - | L1 <: T <: UU
29+
// 3 | LL | U1 | + | LL <: T <: U1
30+
// 4 | NN | U1 | + | T <: U1
31+
// 5 | L1 | U1 | 0 | L1 <: T <: U1
32+
// 6 | LL | UU | 0 | LL <: T <: UU
33+
// 7 | LL | AA | 0 | LL <: T
34+
// 8 | NN | UU | 0 | T <: UU
35+
// 9 | NN | AA | 0 | T
36+
37+
def decide(lb: LB, ub: UB, v: Var, bot: Bot, m: MSe): Act = (lb, ub) match
38+
case (L1, AA) => Min
39+
case (L1, UU) => Min
40+
case (LL, U1) => Max
41+
case (NN, U1) => Max
42+
43+
case (L1, U1) => if m==M || v==V then Min else ToMax
44+
case (LL, UU) => if m==M || v==V then Min else ToMax
45+
case (LL, AA) => if m==M || v==V then Min else ToMax
46+
47+
case (NN, UU) => bot match
48+
case _ if m==M => Max
49+
//case Ok if v==V => Min // removed, i14218 fix
50+
case Fail if v==V => False
51+
case _ => ToMax
52+
53+
case (NN, AA) => bot match
54+
case _ if m==M => Skip
55+
case Ok if v==V => Min
56+
case Fail if v==V => False
57+
case _ => ToMax

tests/neg/i15525.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def element22(
3737
transmittable20.Type / transmittable21.Type
3838
} = ???
3939

40-
def test22 =
40+
def test22 = // error
4141
Resolution(
4242
element22(
4343
Resolution(element0), Resolution(element0), // error // error

tests/pos/i14218.http4s.scala

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// A minimisation from http4s,
2+
// which broke while implementing the fix for i14218.
3+
4+
final class Bar[+F[_]]
5+
object Bar:
6+
def empty[F[_]]: Bar[F] = new Bar[Nothing]
7+
8+
final class Foo[+F[_]]
9+
10+
object Foo:
11+
def apply[F[_]](bar: Bar[F] = Bar.empty): Foo[F] = new Foo
12+
13+
class Test:
14+
def test[F[_]]: Foo[F] = Foo[F]()
15+
16+
//-- [E007] Type Mismatch Error
17+
//12 | def test[F[_]]: Foo[F] = Foo[F]()
18+
// | ^^^^^^
19+
// | Found: Bar[[_] =>> Any]
20+
// | Required: Bar[F]
21+
// |
22+
// | where: F is a type in method t1 with bounds <: [_] =>> Any

tests/pos/i14218.scala

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
class Pet
2+
class Cat extends Pet
3+
4+
class Z1[ S1 <: Pet](val fn: S1 => Unit)
5+
class Z2[ S2 ](val fn: S2 => Unit)
6+
class Z3[-S3 <: Pet](val fn: S3 => Unit)
7+
8+
abstract class Test:
9+
def test =
10+
val r1 = new Z1((_: Pet) => ()); eat[Z1[Pet]](r1) // the case: using the parameter bound in situ infers Z[Nothing]
11+
val r2 = new Z2((_: Pet) => ()); eat[Z2[Pet]](r2) // counter-example: infers as desired without an upper bound
12+
val r3 = new Z3((_: Pet) => ()); eat[Z3[Pet]](r3) // workaround: declare it contravariant
13+
val r4 = new Z1((_: Cat) => ()); eat[Z1[Cat]](r4) // counter-example: infers as desired with a subtype
14+
15+
def eat[T](x: T): Unit

0 commit comments

Comments
 (0)