Skip to content

Commit 2ef89b2

Browse files
Merge pull request #13962 from dwijnand/pattern-typer/arity
Tighten product match logic in irrefutable check
2 parents 2b37257 + 799b9f3 commit 2ef89b2

File tree

5 files changed

+28
-11
lines changed

5 files changed

+28
-11
lines changed

compiler/src/dotty/tools/dotc/transform/patmat/Space.scala

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ trait SpaceLogic {
108108
def decompose(tp: Type): List[Typ]
109109

110110
/** Whether the extractor covers the given type */
111-
def covers(unapp: TermRef, scrutineeTp: Type): Boolean
111+
def covers(unapp: TermRef, scrutineeTp: Type, argLen: Int): Boolean
112112

113113
/** Display space in string format */
114114
def show(sp: Space): String
@@ -186,7 +186,7 @@ trait SpaceLogic {
186186
isSubType(tp1, tp2)
187187
case (Typ(tp1, _), Prod(tp2, fun, ss)) =>
188188
isSubType(tp1, tp2)
189-
&& covers(fun, tp1)
189+
&& covers(fun, tp1, ss.length)
190190
&& isSubspace(Prod(tp2, fun, signature(fun, tp2, ss.length).map(Typ(_, false))), b)
191191
case (Prod(_, fun1, ss1), Prod(_, fun2, ss2)) =>
192192
isSameUnapply(fun1, fun2) && ss1.zip(ss2).forall((isSubspace _).tupled)
@@ -240,7 +240,7 @@ trait SpaceLogic {
240240
else a
241241
case (Typ(tp1, _), Prod(tp2, fun, ss)) =>
242242
// rationale: every instance of `tp1` is covered by `tp2(_)`
243-
if isSubType(tp1, tp2) && covers(fun, tp1) then
243+
if isSubType(tp1, tp2) && covers(fun, tp1, ss.length) then
244244
minus(Prod(tp1, fun, signature(fun, tp1, ss.length).map(Typ(_, false))), b)
245245
else if canDecompose(tp1) then
246246
tryDecompose1(tp1)
@@ -262,6 +262,7 @@ trait SpaceLogic {
262262
a
263263
case (Prod(tp1, fun1, ss1), Prod(tp2, fun2, ss2)) =>
264264
if (!isSameUnapply(fun1, fun2)) return a
265+
if (fun1.symbol.name == nme.unapply && ss1.length != ss2.length) return a
265266

266267
val range = (0 until ss1.size).toList
267268
val cache = Array.fill[Space](ss2.length)(null)
@@ -288,13 +289,13 @@ object SpaceEngine {
288289
/** Is the unapply or unapplySeq irrefutable?
289290
* @param unapp The unapply function reference
290291
*/
291-
def isIrrefutable(unapp: TermRef)(using Context): Boolean = {
292+
def isIrrefutable(unapp: TermRef, argLen: Int)(using Context): Boolean = {
292293
val unappResult = unapp.widen.finalResultType
293294
unappResult.isRef(defn.SomeClass)
294295
|| unappResult <:< ConstantType(Constant(true)) // only for unapply
295296
|| (unapp.symbol.is(Synthetic) && unapp.symbol.owner.linkedClass.is(Case)) // scala2 compatibility
296297
|| unapplySeqTypeElemTp(unappResult).exists // only for unapplySeq
297-
|| productArity(unappResult) > 0
298+
|| isProductMatch(unappResult, argLen)
298299
|| {
299300
val isEmptyTp = extractorMemberType(unappResult, nme.isEmpty, NoSourcePosition)
300301
isEmptyTp <:< ConstantType(Constant(false))
@@ -304,10 +305,10 @@ object SpaceEngine {
304305
/** Is the unapply or unapplySeq irrefutable?
305306
* @param unapp The unapply function tree
306307
*/
307-
def isIrrefutable(unapp: tpd.Tree)(using Context): Boolean = {
308+
def isIrrefutable(unapp: tpd.Tree, argLen: Int)(using Context): Boolean = {
308309
val fun1 = tpd.funPart(unapp)
309310
val funRef = fun1.tpe.asInstanceOf[TermRef]
310-
isIrrefutable(funRef)
311+
isIrrefutable(funRef, argLen)
311312
}
312313
}
313314

@@ -606,8 +607,8 @@ class SpaceEngine(using Context) extends SpaceLogic {
606607
}
607608

608609
/** Whether the extractor covers the given type */
609-
def covers(unapp: TermRef, scrutineeTp: Type): Boolean =
610-
SpaceEngine.isIrrefutable(unapp) || unapp.symbol == defn.TypeTest_unapply && {
610+
def covers(unapp: TermRef, scrutineeTp: Type, argLen: Int): Boolean =
611+
SpaceEngine.isIrrefutable(unapp, argLen) || unapp.symbol == defn.TypeTest_unapply && {
611612
val AppliedType(_, _ :: tp :: Nil) = unapp.prefix.widen.dealias
612613
scrutineeTp <:< tp
613614
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -821,7 +821,7 @@ trait Checking {
821821
recur(pat1, pt)
822822
case UnApply(fn, _, pats) =>
823823
check(pat, pt) &&
824-
(isIrrefutable(fn) || fail(pat, pt)) && {
824+
(isIrrefutable(fn, pats.length) || fail(pat, pt)) && {
825825
val argPts = unapplyArgs(fn.tpe.widen.finalResultType, fn, pats, pat.srcPos)
826826
pats.corresponds(argPts)(recur)
827827
}

tests/patmat/i13737.check

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
14: Pattern Match Exhaustivity: _: Success

tests/patmat/i13737.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
sealed trait Result
2+
3+
case class Success(result: String, next: Int) extends Result {
4+
def isEmpty: Boolean = 10 % 2 == 1
5+
def get: String = result
6+
}
7+
8+
object Success {
9+
def unapply(x: Success): Success = x
10+
}
11+
12+
def main =
13+
val res: Result = ???
14+
res match // error
15+
case Success(v) => v

tests/patmat/irrefutable.check

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
22: Pattern Match Exhaustivity: _: A, _: B, C(_, _)
2-
65: Pattern Match Exhaustivity: ExM(_, _)
2+
65: Pattern Match Exhaustivity: _: M

0 commit comments

Comments
 (0)