Skip to content

Commit 43fc10c

Browse files
committed
Give up on InferExpectedTypeSuite.map/flatMap
But keep the extraction of the instDecision logic, and keep the tests cases I used in studying this change. Also simplify and update InstantiateModel. Martin had tweaked my i14218 fix to make it more conservative: in the (NN, UU) case (i.e. no inferred bounds, only a UU upper bound declared) for covariant type parameters, revert back to minimising to Nothing rather than maximising to the declared bound.
1 parent 5aaea2f commit 43fc10c

File tree

6 files changed

+138
-56
lines changed

6 files changed

+138
-56
lines changed

Diff for: compiler/src/dotty/tools/dotc/typer/Inferencing.scala

+35-20
Original file line numberDiff line numberDiff line change
@@ -240,25 +240,12 @@ object Inferencing {
240240
&& {
241241
var fail = false
242242
var skip = false
243-
val direction = instDirection(tvar.origin)
244-
if minimizeSelected then
245-
if direction <= 0 && tvar.hasLowerBound then
246-
skip = instantiate(tvar, fromBelow = true)
247-
else if direction >= 0 && tvar.hasUpperBound then
248-
skip = instantiate(tvar, fromBelow = false)
249-
// else hold off instantiating unbounded unconstrained variable
250-
else if direction != 0 then
251-
skip = instantiate(tvar, fromBelow = direction < 0)
252-
else if variance >= 0 && tvar.hasLowerBound then
253-
skip = instantiate(tvar, fromBelow = true)
254-
else if (variance > 0 || variance == 0 && !tvar.hasUpperBound)
255-
&& force.ifBottom == IfBottom.ok
256-
then // if variance == 0, prefer upper bound if one is given
257-
skip = instantiate(tvar, fromBelow = true)
258-
else if variance >= 0 && force.ifBottom == IfBottom.fail then
259-
fail = true
260-
else
261-
toMaximize = tvar :: toMaximize
243+
instDecision(tvar, variance, minimizeSelected, force.ifBottom) match
244+
case Decision.Min => skip = instantiate(tvar, fromBelow = true)
245+
case Decision.Max => skip = instantiate(tvar, fromBelow = false)
246+
case Decision.Skip => // hold off instantiating unbounded unconstrained variable
247+
case Decision.Fail => fail = true
248+
case Decision.ToMax => toMaximize ::= tvar
262249
!fail && (skip || foldOver(x, tvar))
263250
}
264251
case tp => foldOver(x, tp)
@@ -452,9 +439,32 @@ object Inferencing {
452439
if (!cmp.isSubTypeWhenFrozen(constrained.lo, original.lo)) 1 else 0
453440
val approxAbove =
454441
if (!cmp.isSubTypeWhenFrozen(original.hi, constrained.hi)) 1 else 0
442+
//println(i"instDirection($param) = $approxAbove - $approxBelow original=[$original] constrained=[$constrained]")
455443
approxAbove - approxBelow
456444
}
457445

446+
/** The instantiation decision for given poly param computed from the constraint. */
447+
enum Decision { case Min; case Max; case ToMax; case Skip; case Fail }
448+
private def instDecision(tvar: TypeVar, v: Int, minimizeSelected: Boolean, ifBottom: IfBottom)(using Context): Decision =
449+
import Decision.*
450+
val direction = instDirection(tvar.origin)
451+
val dec = if minimizeSelected then
452+
if direction <= 0 && tvar.hasLowerBound then Min
453+
else if direction >= 0 && tvar.hasUpperBound then Max
454+
else Skip
455+
else if direction != 0 then if direction < 0 then Min else Max
456+
else if tvar.hasLowerBound then if v >= 0 then Min else ToMax
457+
else ifBottom match
458+
// What's left are unconstrained tvars with at most a non-Any param upperbound:
459+
// * IfBottom.flip will always maximise to the param upperbound, for all variances
460+
// * IfBottom.fail will fail the IFD check, for covariant or invariant tvars, maximise contravariant tvars
461+
// * IfBottom.ok will minimise to Nothing covariant and unbounded invariant tvars, and max to Any the others
462+
case IfBottom.ok => if v > 0 || v == 0 && !tvar.hasUpperBound then Min else ToMax // prefer upper bound if one is given
463+
case IfBottom.fail => if v >= 0 then Fail else ToMax
464+
case ifBottom_flip => ToMax
465+
//println(i"instDecision($tvar, v=v, minimizedSelected=$minimizeSelected, $ifBottom) dir=$direction = $dec")
466+
dec
467+
458468
/** Following type aliases and stripping refinements and annotations, if one arrives at a
459469
* class type reference where the class has a companion module, a reference to
460470
* that companion module. Otherwise NoType
@@ -651,7 +661,7 @@ trait Inferencing { this: Typer =>
651661

652662
val ownedVars = state.ownedVars
653663
if (ownedVars ne locked) && !ownedVars.isEmpty then
654-
val qualifying = ownedVars -- locked
664+
val qualifying = (ownedVars -- locked).toList
655665
if (!qualifying.isEmpty) {
656666
typr.println(i"interpolate $tree: ${tree.tpe.widen} in $state, pt = $pt, owned vars = ${state.ownedVars.toList}%, %, qualifying = ${qualifying.toList}%, %, previous = ${locked.toList}%, % / ${state.constraint}")
657667
val resultAlreadyConstrained =
@@ -687,6 +697,10 @@ trait Inferencing { this: Typer =>
687697

688698
def constraint = state.constraint
689699

700+
trace(i"interpolateTypeVars($tree: ${tree.tpe}, $pt, $qualifying)", typr, (_: Any) => i"$qualifying\n$constraint\n${ctx.gadt}") {
701+
//println(i"$constraint")
702+
//println(i"${ctx.gadt}")
703+
690704
/** Values of this type report type variables to instantiate with variance indication:
691705
* +1 variable appears covariantly, can be instantiated from lower bound
692706
* -1 variable appears contravariantly, can be instantiated from upper bound
@@ -804,6 +818,7 @@ trait Inferencing { this: Typer =>
804818
end doInstantiate
805819

806820
doInstantiate(filterByDeps(toInstantiate))
821+
}
807822
}
808823
end if
809824
tree

Diff for: compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala

+3-1
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,11 @@ import config.Printers.typr
1818
import Inferencing.*
1919
import ErrorReporting.*
2020
import util.SourceFile
21+
import util.Spans.{NoSpan, Span}
2122
import TypeComparer.necessarySubType
23+
import reporting.*
2224

2325
import scala.annotation.internal.sharable
24-
import dotty.tools.dotc.util.Spans.{NoSpan, Span}
2526

2627
object ProtoTypes {
2728

@@ -83,6 +84,7 @@ object ProtoTypes {
8384
* fits the given expected result type.
8485
*/
8586
def constrainResult(mt: Type, pt: Type)(using Context): Boolean =
87+
trace(i"constrainResult($mt, $pt)", typr):
8688
val savedConstraint = ctx.typerState.constraint
8789
val res = pt.widenExpr match {
8890
case pt: FunProto =>

Diff for: compiler/test/dotty/tools/dotc/typer/InstantiateModel.scala

+23-26
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,16 @@ package typer
44

55
// Modelling the decision in IsFullyDefined
66
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.*
7+
enum LB { case NN; case LL; case L1 }; import LB.*
8+
enum UB { case AA; case UU; case U1 }; import UB.*
9+
enum Decision { case Min; case Max; case ToMax; case Skip; case Fail }; import Decision.*
1310

1411
// NN/AA = Nothing/Any
1512
// LL/UU = the original bounds, on the type parameter
1613
// L1/U1 = the constrained bounds, on the type variable
17-
// V = variance >= 0 ("non-contravariant")
18-
// MSe = minimisedSelected
19-
// Bot = IfBottom
2014
// ToMax = delayed maximisation, via addition to toMaximize
2115
// Skip = minimisedSelected "hold off instantiating"
22-
// False = return false
16+
// Fail = IfBottom.fail's bail option
2317

2418
// there are 9 combinations:
2519
// # | LB | UB | d | // d = direction
@@ -34,24 +28,27 @@ object InstantiateModel:
3428
// 8 | NN | UU | 0 | T <: UU
3529
// 9 | NN | AA | 0 | T
3630

37-
def decide(lb: LB, ub: UB, v: Var, bot: Bot, m: MSe): Act = (lb, ub) match
31+
def instDecision(lb: LB, ub: UB, v: Int, ifBottom: IfBottom, min: Boolean) = (lb, ub) match
3832
case (L1, AA) => Min
3933
case (L1, UU) => Min
4034
case (LL, U1) => Max
4135
case (NN, U1) => Max
4236

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
37+
case (L1, U1) => if min then Min else pickVar(v, Min, Min, ToMax)
38+
case (LL, UU) => if min then Min else pickVar(v, Min, Min, ToMax)
39+
case (LL, AA) => if min then Min else pickVar(v, Min, Min, ToMax)
40+
41+
case (NN, UU) => ifBottom match
42+
case _ if min => Max
43+
case IfBottom.ok => pickVar(v, Min, ToMax, ToMax)
44+
case IfBottom.fail => pickVar(v, Fail, Fail, ToMax)
45+
case IfBottom.flip => ToMax
46+
47+
case (NN, AA) => ifBottom match
48+
case _ if min => Skip
49+
case IfBottom.ok => pickVar(v, Min, Min, ToMax)
50+
case IfBottom.fail => pickVar(v, Fail, Fail, ToMax)
51+
case IfBottom.flip => ToMax
52+
53+
def pickVar[A](v: Int, cov: A, inv: A, con: A) =
54+
if v > 0 then cov else if v == 0 then inv else con

Diff for: presentation-compiler/test/dotty/tools/pc/tests/InferExpectedTypeSuite.scala

+6-9
Original file line numberDiff line numberDiff line change
@@ -221,34 +221,31 @@ class InferExpectedTypeSuite extends BasePCSuite:
221221
|""".stripMargin
222222
)
223223

224-
@Ignore("Generic functions are not handled correctly.")
225224
@Test def flatmap =
226225
check(
227226
"""|val _ : List[Int] = List().flatMap(_ => @@)
228227
|""".stripMargin,
229-
"""|IterableOnce[Int]
230-
|""".stripMargin
228+
"""|IterableOnce[Nothing]
229+
|""".stripMargin // ideally IterableOnce[Int], but can't change interpolateTypeVars
231230
)
232231

233-
@Ignore("Generic functions are not handled correctly.")
234232
@Test def map =
235233
check(
236234
"""|val _ : List[Int] = List().map(_ => @@)
237235
|""".stripMargin,
238-
"""|Int
239-
|""".stripMargin
236+
"""|Nothing
237+
|""".stripMargin // ideally Int, but can't change interpolateTypeVars
240238
)
241239

242-
@Ignore("Generic functions are not handled correctly.")
243240
@Test def `for-comprehension` =
244241
check(
245242
"""|val _ : List[Int] =
246243
| for {
247244
| _ <- List("a", "b")
248245
| } yield @@
249246
|""".stripMargin,
250-
"""|Int
251-
|""".stripMargin
247+
"""|Nothing
248+
|""".stripMargin // ideally Int, but can't change interpolateTypeVars
252249
)
253250

254251
// bounds

Diff for: tests/pos/i21390.TrieMap.scala

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Minimised from scala.collection.concurrent.LNode
2+
// Useful as a minimisation of how,
3+
// If we were to change the type interpolation
4+
// to minimise to the inferred "X" type,
5+
// then this is a minimisation of how the (ab)use of
6+
// GADT constraints to handle class type params
7+
// can fail PostTyper, -Ytest-pickler, and probably others.
8+
9+
import scala.language.experimental.captureChecking
10+
11+
class Foo[X](xs: List[X]):
12+
def this(a: X, b: X) = this(if (a == b) then a :: Nil else a :: b :: Nil)

Diff for: tests/pos/i21390.zio.scala

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// A minimisation of a community build failure in PR 21390
2+
// To see why changing the instantiation direction in interpolateTypeVars
3+
// using the same logic as IsFullyDefined.
4+
class Has[A]
5+
object Has:
6+
class Union[B, C]
7+
object Union:
8+
given HasHasUnion[B0 <: Has[?], C0 <: Has[?]]: Union[B0, C0] = ???
9+
10+
class Lay[+D]:
11+
def and1[B1 >: D, C1](that: Lay[C1])(using Has.Union[B1, C1]): Lay[B1 & C1] = ???
12+
def and2[B2 >: D, C2](that: Lay[C2])(using Has.Union[B2, C2]): Lay[B2 & C2] = ???
13+
14+
class J; type X = Has[J]
15+
class K; type Y = Has[K]
16+
class L; type Z = Has[L]
17+
18+
def t1(x: Lay[X], y: Lay[Y], z: Lay[Z]): Lay[X & Y & Z] = x.and1(y).and2(z)
19+
20+
/*
21+
22+
Here's what goes wrong in the tvar instantiation, in method t1:
23+
24+
1) <== constrainResult(method and1, (using x$2: Union[B1, C1]): Lay[B1 & C1], ?{ and2: ? }) = true
25+
2) ==> Has.Union[B0, C0] <: Has.Union[B1, C1 := Y]?
26+
3) <== Has.Union[B0, C0] <: Has.Union[B1, C1 := Y] = OK
27+
28+
1) B1 >: X B2 >: B1 & C1
29+
2) B1 >: X C1 := Y B2 >: B1 & Y B0 <: Has[?] C0 <: Has[?]
30+
3) B1 >: X <: Has[?] C1 := Y B2 >: B1 & Y B0 := B1 C0 := Y
31+
32+
1) Check that the result of and1 fits the expected .and2 call, inferring any necessary constraints
33+
2) Initiate the check that calling HasHasUnion matches the needed Has.Union[B1, C1] parameter
34+
3) In inferring that the need B0 := B1 and C0 := Y, we end up inferring B0's `<: Has[?]` on B1.
35+
36+
4a) <== B1.instantiate(fromBelow = true ) = X
37+
4b) <== B1.instantiate(fromBelow = false) = Has[?]
38+
5a) <== B2.instantiate(fromBelow = true) = X & Y
39+
5b) <== B2.instantiate(fromBelow = true) = Y
40+
6) <== constrainResult(method and2, (using x$2: Has.Union[B2, C2]): Lay[B2 & C2], Lay[X & Y & Z]) = true
41+
42+
4a) B2 >: X & Y
43+
4b) B2 >: Y & Has[?]
44+
5a) B2 := X & Y
45+
5b) B2 := Y
46+
6a) B2 >: X & Y C2 <: Z
47+
6b) B2 >: Y C2 <: X & Z
48+
49+
4) With the extra upper bound constraint, we end up maximising to Has[?] (4b) instead of minimising to X (4a)
50+
5) Which leads to instantiating B2 to just Y (5b) instead of X & Y (5a)
51+
6) Which leads the constraints from the result of and2 to infer X & Z (6b) instead of just Z (6a)
52+
53+
-- [E007] Type Mismatch Error: tests/pos/i21390.zio.scala:14:73 ------------------------------------
54+
14 |def t1(x: Lay[X], y: Lay[Y], z: Lay[Z]): Lay[X & Y & Z] = x.and1(y).and2(z)
55+
| ^
56+
| Found: (z : Lay[Z])
57+
| Required: Lay[X & Z]
58+
59+
*/

0 commit comments

Comments
 (0)