From c0df9b939a0ba23c414de041fd12a33e2aed1ce9 Mon Sep 17 00:00:00 2001 From: Piotr Paradzinski Date: Mon, 9 Mar 2026 18:43:32 +0100 Subject: [PATCH 1/6] Restore MatchTypeNoCases warning for irreducible match types Signed-off-by: Piotr Paradzinski --- .../dotty/tools/dotc/core/TypeComparer.scala | 17 +++++++++++------ tests/warn/i23822.scala | 16 ++++++++++++++++ 2 files changed, 27 insertions(+), 6 deletions(-) create mode 100644 tests/warn/i23822.scala diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 746be4ae5766..3b81ad2e8077 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -3947,14 +3947,16 @@ class MatchReducer(initctx: Context) extends TypeComparer(initctx) { def matchMissingCaptures(spec: MatchTypeCaseSpec.MissingCaptures): MatchResult = MatchResult.Stuck - def recur(remaining: List[MatchTypeCaseSpec]): Type = remaining match + case class MatchTypeNoCasesKey(pos: String, msg: String) + + def recur(remaining: List[MatchTypeCaseSpec], seen: mutable.Set[MatchTypeNoCasesKey]): Type = remaining match case (cas: MatchTypeCaseSpec.LegacyPatMat) :: _ if sourceVersion.isAtLeast(SourceVersion.`3.4`) => val errorText = MatchTypeTrace.illegalPatternText(scrut, cas) ErrorType(reporting.MatchTypeLegacyPattern(errorText)) case cas :: remaining1 => matchCase(cas) match case MatchResult.Disjoint => - recur(remaining1) + recur(remaining1, seen) case MatchResult.Stuck => MatchTypeTrace.stuck(scrut, cas, remaining1) NoType @@ -3974,14 +3976,17 @@ class MatchReducer(initctx: Context) extends TypeComparer(initctx) { MatchTypeTrace.emptyScrutinee(scrut) NoType case Nil => - /* TODO warn ? then re-enable warn/12974.scala:26 val noCasesText = MatchTypeTrace.noMatchesText(scrut, cases) - report.warning(reporting.MatchTypeNoCases(noCasesText), pos = ???) - */ + val pos = ctx.source.atSpan(ctx.owner.span) + if !scrut.isInstanceOf[TypeVar] then + val key = MatchTypeNoCasesKey(pos.toString, noCasesText) + if seen.add(key) then + report.warning(reporting.MatchTypeNoCases(noCasesText), pos) + MatchTypeTrace.noMatches(scrut, cases) NoType - inFrozenConstraint(recur(cases)) + inFrozenConstraint(recur(cases, mutable.HashSet.empty)) } } diff --git a/tests/warn/i23822.scala b/tests/warn/i23822.scala new file mode 100644 index 000000000000..3c7aecb1fbcc --- /dev/null +++ b/tests/warn/i23822.scala @@ -0,0 +1,16 @@ +object MatchTypes: + + type ConstituentPartOf[A] = A match + case BigInt => Int + case String => Char + + def lastPartOf[A](thing: A): ConstituentPartOf[A] = thing match + case number: BigInt => (number % 10).toInt + case string: String => string.charAt(string.length - 1) + + def main(args: Array[String]): Unit = + val lastPartOfSomethingElse = lastPartOf(BigDecimal("10")) // warn + // ^ + // Match type reduction failed since selector BigDecimal + // matches none of the cases + println(lastPartOfSomethingElse) From 8b538b9ecf5384fdcacfe47a0ff8e01b3e6bc81c Mon Sep 17 00:00:00 2001 From: Piotr Paradzinski Date: Mon, 9 Mar 2026 23:43:32 +0100 Subject: [PATCH 2/6] MatchTypeNoCases warning - remove deduplication in favour of condition that filters warnings: - os.exists => only warn when there is a real source position - Mode.ReadPositions => avoid internal/speculative contexts - ctx.owner.isTerm => prefer user-facing term-level situations - !TypeVar => skip unstable intermediate typing states Signed-off-by: Piotr Paradzinski --- .../src/dotty/tools/dotc/core/TypeComparer.scala | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 3b81ad2e8077..a6ad354b90b1 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -3947,16 +3947,14 @@ class MatchReducer(initctx: Context) extends TypeComparer(initctx) { def matchMissingCaptures(spec: MatchTypeCaseSpec.MissingCaptures): MatchResult = MatchResult.Stuck - case class MatchTypeNoCasesKey(pos: String, msg: String) - - def recur(remaining: List[MatchTypeCaseSpec], seen: mutable.Set[MatchTypeNoCasesKey]): Type = remaining match + def recur(remaining: List[MatchTypeCaseSpec]): Type = remaining match case (cas: MatchTypeCaseSpec.LegacyPatMat) :: _ if sourceVersion.isAtLeast(SourceVersion.`3.4`) => val errorText = MatchTypeTrace.illegalPatternText(scrut, cas) ErrorType(reporting.MatchTypeLegacyPattern(errorText)) case cas :: remaining1 => matchCase(cas) match case MatchResult.Disjoint => - recur(remaining1, seen) + recur(remaining1) case MatchResult.Stuck => MatchTypeTrace.stuck(scrut, cas, remaining1) NoType @@ -3978,15 +3976,13 @@ class MatchReducer(initctx: Context) extends TypeComparer(initctx) { case Nil => val noCasesText = MatchTypeTrace.noMatchesText(scrut, cases) val pos = ctx.source.atSpan(ctx.owner.span) - if !scrut.isInstanceOf[TypeVar] then - val key = MatchTypeNoCasesKey(pos.toString, noCasesText) - if seen.add(key) then - report.warning(reporting.MatchTypeNoCases(noCasesText), pos) + if pos.exists && ctx.mode.is(Mode.ReadPositions) && ctx.owner.isTerm && !scrut.isInstanceOf[TypeVar] then + report.warning(reporting.MatchTypeNoCases(noCasesText), pos) MatchTypeTrace.noMatches(scrut, cases) NoType - inFrozenConstraint(recur(cases, mutable.HashSet.empty)) + inFrozenConstraint(recur(cases)) } } From d6d3a1f69eab1a2a7d30917b6865d7db987c9d10 Mon Sep 17 00:00:00 2001 From: Piotr Paradzinski Date: Tue, 10 Mar 2026 01:48:29 +0100 Subject: [PATCH 3/6] MatchTypeNoCases warning - add condition to emit warning: ctx.phase.isTyper && !ctx.reporter.hasErrors Signed-off-by: Piotr Paradzinski --- .../src/dotty/tools/dotc/core/TypeComparer.scala | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index a6ad354b90b1..43379530b834 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -3976,7 +3976,18 @@ class MatchReducer(initctx: Context) extends TypeComparer(initctx) { case Nil => val noCasesText = MatchTypeTrace.noMatchesText(scrut, cases) val pos = ctx.source.atSpan(ctx.owner.span) - if pos.exists && ctx.mode.is(Mode.ReadPositions) && ctx.owner.isTerm && !scrut.isInstanceOf[TypeVar] then + val isStableScrut = !scrut.isInstanceOf[TypeVar] + val noErrorYet = !ctx.reporter.hasErrors + + // Emit warn for user-facing term-level typing, and avoid intermediate / redundant reports + val doEmit = + pos.exists + && ctx.mode.is(Mode.ReadPositions) + && ctx.owner.isTerm + && isStableScrut + && ctx.phase.isTyper + && noErrorYet + if doEmit then report.warning(reporting.MatchTypeNoCases(noCasesText), pos) MatchTypeTrace.noMatches(scrut, cases) From 87e015e4449b1851b90cedb9940fb69abd1dfc18 Mon Sep 17 00:00:00 2001 From: Piotr Paradzinski Date: Thu, 12 Mar 2026 13:51:27 +0100 Subject: [PATCH 4/6] Restore example to expect MatchTypeNoCases warn previously disabled Signed-off-by: Piotr Paradzinski --- tests/warn/12974.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/warn/12974.scala b/tests/warn/12974.scala index 45029602296f..5d7daa2b3ae7 100644 --- a/tests/warn/12974.scala +++ b/tests/warn/12974.scala @@ -23,7 +23,7 @@ object RecMap { def main(args: Array[String]) = import Record._ - val foo: Any = Rec.empty.fetch("foo") // TODO + val foo: Any = Rec.empty.fetch("foo") // warn // ^ // Match type reduction failed since selector EmptyTuple.type // matches none of the cases From 558415cfc7c284b95d8b9cbcc9bd215bf411a323 Mon Sep 17 00:00:00 2001 From: Piotr Paradzinski Date: Mon, 16 Mar 2026 16:49:06 +0100 Subject: [PATCH 5/6] MatchTypeNoCases - drop condition ctx.mode.is(Mode.ReadPositions) Signed-off-by: Piotr Paradzinski --- compiler/src/dotty/tools/dotc/core/TypeComparer.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 43379530b834..117ac6244dbd 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -3982,7 +3982,6 @@ class MatchReducer(initctx: Context) extends TypeComparer(initctx) { // Emit warn for user-facing term-level typing, and avoid intermediate / redundant reports val doEmit = pos.exists - && ctx.mode.is(Mode.ReadPositions) && ctx.owner.isTerm && isStableScrut && ctx.phase.isTyper From caaf3b3ede6a0aa21da3985ef19c2381167141a5 Mon Sep 17 00:00:00 2001 From: Piotr Paradzinski Date: Tue, 17 Mar 2026 09:57:09 +0100 Subject: [PATCH 6/6] MatchTypeNoCases - drop condition !ctx.reporter.hasErrors Signed-off-by: Piotr Paradzinski --- .../src/dotty/tools/dotc/core/TypeComparer.scala | 2 -- tests/neg/i12049.check | 16 ++++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 117ac6244dbd..70051a713bb3 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -3977,7 +3977,6 @@ class MatchReducer(initctx: Context) extends TypeComparer(initctx) { val noCasesText = MatchTypeTrace.noMatchesText(scrut, cases) val pos = ctx.source.atSpan(ctx.owner.span) val isStableScrut = !scrut.isInstanceOf[TypeVar] - val noErrorYet = !ctx.reporter.hasErrors // Emit warn for user-facing term-level typing, and avoid intermediate / redundant reports val doEmit = @@ -3985,7 +3984,6 @@ class MatchReducer(initctx: Context) extends TypeComparer(initctx) { && ctx.owner.isTerm && isStableScrut && ctx.phase.isTyper - && noErrorYet if doEmit then report.warning(reporting.MatchTypeNoCases(noCasesText), pos) diff --git a/tests/neg/i12049.check b/tests/neg/i12049.check index e0c2d498f119..8ec1e2da932b 100644 --- a/tests/neg/i12049.check +++ b/tests/neg/i12049.check @@ -103,3 +103,19 @@ | Therefore, reduction cannot advance to the remaining case | | case B => String +-- [E184] Type Warning: tests/neg/i12049.scala:14:4 -------------------------------------------------------------------- +14 |val y3: String = ??? : Last[Int *: Int *: Boolean *: String *: EmptyTuple] // error + | ^ + | Match type reduction failed since selector EmptyTuple + | matches none of the cases + | + | case _ *: _ *: t => Last[t] + | case t *: EmptyTuple => t +-- [E184] Type Warning: tests/neg/i12049.scala:22:4 -------------------------------------------------------------------- +22 |val z3: (A, B, A) = ??? : Reverse[(A, B, A)] // error + | ^ + | Match type reduction failed since selector A *: EmptyTuple.type + | matches none of the cases + | + | case t1 *: t2 *: ts => Tuple.Concat[Reverse[ts], (t2, t1)] + | case EmptyTuple => EmptyTuple