From e1149071d42fdda990589d4f50c7590a896be7a9 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Thu, 6 Mar 2025 00:00:11 +0100 Subject: [PATCH] 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. --- .../src/dotty/tools/dotc/typer/Applications.scala | 14 +++++++------- tests/pos/i22713.scala | 14 ++++++++++++++ 2 files changed, 21 insertions(+), 7 deletions(-) create mode 100644 tests/pos/i22713.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 3e29aa5ee28a..9042b823bdc4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -2125,19 +2125,19 @@ trait Applications extends Compatibility { * Using an approximated result type is necessary to avoid false negatives * due to incomplete type inference such as in tests/pos/i21410.scala and tests/pos/i21410b.scala. */ - def resultConforms(altSym: Symbol, altType: Type, resultType: Type)(using Context): Boolean = + def resultConforms(altSym: Symbol, altType: Type, resultType: Type, avoidFalseNegatives: Boolean)(using Context): Boolean = resultType.revealIgnored match { case resultType: ValueType => altType.widen match { - case tp: PolyType => resultConforms(altSym, tp.resultType, resultType) + case tp: PolyType => resultConforms(altSym, tp.resultType, resultType, avoidFalseNegatives) case tp: MethodType => val wildRes = wildApprox(tp.resultType) - class ResultApprox extends AvoidWildcardsMap: - // Avoid false negatives by approximating to a lower bound + class UnderApprox extends AvoidWildcardsMap: + // Prefer false positives to false negatives by approximating to a lower bound variance = -1 - val approx = ResultApprox()(wildRes) + val approx = if avoidFalseNegatives then UnderApprox()(wildRes) else wildRes constrainResult(altSym, approx, resultType) case _ => true } @@ -2157,9 +2157,9 @@ trait Applications extends Compatibility { * do they prune much, on average. */ def adaptByResult(chosen: TermRef, alts: List[TermRef]) = pt match { - case pt: FunProto if !explore(resultConforms(chosen.symbol, chosen, pt.resultType)) => + case pt: FunProto if !explore(resultConforms(chosen.symbol, chosen, pt.resultType, avoidFalseNegatives = true)) => val conformingAlts = alts.filterConserve(alt => - (alt ne chosen) && explore(resultConforms(alt.symbol, alt, pt.resultType))) + (alt ne chosen) && explore(resultConforms(alt.symbol, alt, pt.resultType, avoidFalseNegatives = false))) conformingAlts match { case Nil => chosen case alt2 :: Nil => alt2 diff --git a/tests/pos/i22713.scala b/tests/pos/i22713.scala new file mode 100644 index 000000000000..afd0d7fccd20 --- /dev/null +++ b/tests/pos/i22713.scala @@ -0,0 +1,14 @@ +trait Invariant[T] + +trait Foo +trait Bar[T] + +trait Worker: + def schedule(command: Foo): Invariant[?] + def schedule[V](callable: Bar[V]): Invariant[V] + +object Test: + def test(worker: Worker, foo: Foo): Option[Unit] = + Some(worker.schedule(foo)) + // error: Found: Foo + // Required: Bar[V]