Skip to content

Commit f1674c5

Browse files
committed
Tune usage of resultConforms to prefer the chosen alternative
`resultConforms` should only make us change our mind when we're sure that the chosen alternative does not fit the result type but another alternative would. The problem is that due to approximations and adaptation, we can guess wrong on both counts. In i22713 in particular, we guessed that the chosen alternative does not work (but it does because of Unit insertion) and that another alternative would, but it doesn't because `resultConforms` prefers false positives to false negatives (the result type is under-approximated to `Nothing` which is indeed a subtype of `Unit`). We only really need to prefer false positives to false negatives when considering whether to discard the currently chosen alternative (because there's probably a good reason we chose it in the first place), so this commit introduces a parameter to `resultConforms` to control this. Fixes #22713.
1 parent 332fceb commit f1674c5

File tree

2 files changed

+20
-6
lines changed

2 files changed

+20
-6
lines changed

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

+6-6
Original file line numberDiff line numberDiff line change
@@ -2125,19 +2125,19 @@ trait Applications extends Compatibility {
21252125
* Using an approximated result type is necessary to avoid false negatives
21262126
* due to incomplete type inference such as in tests/pos/i21410.scala and tests/pos/i21410b.scala.
21272127
*/
2128-
def resultConforms(altSym: Symbol, altType: Type, resultType: Type)(using Context): Boolean =
2128+
def resultConforms(altSym: Symbol, altType: Type, resultType: Type, avoidFalseNegatives: Boolean)(using Context): Boolean =
21292129
resultType.revealIgnored match {
21302130
case resultType: ValueType =>
21312131
altType.widen match {
2132-
case tp: PolyType => resultConforms(altSym, tp.resultType, resultType)
2132+
case tp: PolyType => resultConforms(altSym, tp.resultType, resultType, avoidFalseNegatives)
21332133
case tp: MethodType =>
21342134
val wildRes = wildApprox(tp.resultType)
21352135

21362136
class ResultApprox extends AvoidWildcardsMap:
2137-
// Avoid false negatives by approximating to a lower bound
2137+
// Prefer false negatives to false positives by approximating to a lower bound
21382138
variance = -1
21392139

2140-
val approx = ResultApprox()(wildRes)
2140+
val approx = if avoidFalseNegatives then ResultApprox()(wildRes) else wildRes
21412141
constrainResult(altSym, approx, resultType)
21422142
case _ => true
21432143
}
@@ -2157,9 +2157,9 @@ trait Applications extends Compatibility {
21572157
* do they prune much, on average.
21582158
*/
21592159
def adaptByResult(chosen: TermRef, alts: List[TermRef]) = pt match {
2160-
case pt: FunProto if !explore(resultConforms(chosen.symbol, chosen, pt.resultType)) =>
2160+
case pt: FunProto if !explore(resultConforms(chosen.symbol, chosen, pt.resultType, avoidFalseNegatives = true)) =>
21612161
val conformingAlts = alts.filterConserve(alt =>
2162-
(alt ne chosen) && explore(resultConforms(alt.symbol, alt, pt.resultType)))
2162+
(alt ne chosen) && explore(resultConforms(alt.symbol, alt, pt.resultType, avoidFalseNegatives = false)))
21632163
conformingAlts match {
21642164
case Nil => chosen
21652165
case alt2 :: Nil => alt2

tests/pos/i22713.scala

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
trait Invariant[T]
2+
3+
trait Foo
4+
trait Bar[T]
5+
6+
trait Worker:
7+
def schedule(command: Foo): Invariant[?]
8+
def schedule[V](callable: Bar[V]): Invariant[V]
9+
10+
object Test:
11+
def test(worker: Worker, foo: Foo): Option[Unit] =
12+
Some(worker.schedule(foo))
13+
// error: Found: Foo
14+
// Required: Bar[V]

0 commit comments

Comments
 (0)