Skip to content

Commit 08cf458

Browse files
committed
Fix #6490: Add spec for irrefutable extractors
1 parent fcd4315 commit 08cf458

File tree

7 files changed

+114
-10
lines changed

7 files changed

+114
-10
lines changed

compiler/src/dotty/tools/dotc/ast/Desugar.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -709,7 +709,8 @@ object desugar {
709709
val methName = if (hasRepeatedParam) nme.unapplySeq else nme.unapply
710710
val unapplyParam = makeSyntheticParameter(tpt = classTypeRef)
711711
val unapplyRHS = if (arity == 0) Literal(Constant(true)) else Ident(unapplyParam.name)
712-
DefDef(methName, derivedTparams, (unapplyParam :: Nil) :: Nil, TypeTree(), unapplyRHS)
712+
val unapplyResTp = if (arity == 0) Literal(Constant(true)) else TypeTree()
713+
DefDef(methName, derivedTparams, (unapplyParam :: Nil) :: Nil, unapplyResTp, unapplyRHS)
713714
.withMods(synthetic)
714715
}
715716
companionDefs(companionParent, applyMeths ::: unapplyMeth :: companionMembers)

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

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -285,12 +285,15 @@ object SpaceEngine {
285285
/** Is the unapply irrefutable?
286286
* @param unapp The unapply function reference
287287
*/
288-
def isIrrefutableUnapply(unapp: tpd.Tree)(implicit ctx: Context): Boolean = {
288+
def isIrrefutableUnapply(unapp: tpd.Tree, patSize: Int)(implicit ctx: Context): Boolean = {
289289
val unappResult = unapp.tpe.widen.finalResultType
290290
unappResult.isRef(defn.SomeClass) ||
291-
unappResult =:= ConstantType(Constant(true)) ||
292-
(unapp.symbol.is(Synthetic) && unapp.symbol.owner.linkedClass.is(Case)) ||
293-
productArity(unappResult) > 0
291+
unappResult <:< ConstantType(Constant(true)) ||
292+
(unapp.symbol.is(Synthetic) && unapp.symbol.owner.linkedClass.is(Case)) || // scala2 compatibility
293+
(patSize != -1 && productArity(unappResult) == patSize) || {
294+
val isEmptyTp = extractorMemberType(unappResult, nme.isEmpty, unapp.sourcePos)
295+
isEmptyTp <:< ConstantType(Constant(true))
296+
}
294297
}
295298
}
296299

@@ -345,12 +348,12 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
345348
else {
346349
val (arity, elemTp, resultTp) = unapplySeqInfo(fun.tpe.widen.finalResultType, fun.sourcePos)
347350
if (elemTp.exists)
348-
Prod(erase(pat.tpe.stripAnnots), fun.tpe, fun.symbol, projectSeq(pats) :: Nil, isIrrefutableUnapply(fun))
351+
Prod(erase(pat.tpe.stripAnnots), fun.tpe, fun.symbol, projectSeq(pats) :: Nil, isIrrefutableUnapply(fun, -1))
349352
else
350-
Prod(erase(pat.tpe.stripAnnots), fun.tpe, fun.symbol, pats.take(arity - 1).map(project) :+ projectSeq(pats.drop(arity - 1)),isIrrefutableUnapply(fun))
353+
Prod(erase(pat.tpe.stripAnnots), fun.tpe, fun.symbol, pats.take(arity - 1).map(project) :+ projectSeq(pats.drop(arity - 1)), full = true)
351354
}
352355
else
353-
Prod(erase(pat.tpe.stripAnnots), fun.tpe, fun.symbol, pats.map(project), isIrrefutableUnapply(fun))
356+
Prod(erase(pat.tpe.stripAnnots), fun.tpe, fun.symbol, pats.map(project), isIrrefutableUnapply(fun, pats.length))
354357
case Typed(pat @ UnApply(_, _, _), _) => project(pat)
355358
case Typed(expr, tpt) =>
356359
Typ(erase(expr.tpe.stripAnnots), true)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ object Applications {
7070
* parameterless `isEmpty` member of result type `Boolean`.
7171
*/
7272
def isGetMatch(tp: Type, errorPos: SourcePosition = NoSourcePosition)(implicit ctx: Context): Boolean =
73-
extractorMemberType(tp, nme.isEmpty, errorPos).isRef(defn.BooleanClass) &&
73+
extractorMemberType(tp, nme.isEmpty, errorPos).widenSingleton.isRef(defn.BooleanClass) &&
7474
extractorMemberType(tp, nme.get, errorPos).exists
7575

7676
/** If `getType` is of the form:

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -647,7 +647,7 @@ trait Checking {
647647
recur(pat1, pt)
648648
case UnApply(fn, _, pats) =>
649649
check(pat, pt) &&
650-
(isIrrefutableUnapply(fn) || fail(pat, pt)) && {
650+
(isIrrefutableUnapply(fn, pats.length) || fail(pat, pt)) && {
651651
val argPts = unapplyArgs(fn.tpe.widen.finalResultType, fn, pats, pat.sourcePos)
652652
pats.corresponds(argPts)(recur)
653653
}

docs/docs/reference/changed-features/pattern-matching.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,13 @@ and `S` conforms to one of the following matches:
5050
The former form of `unapply` has higher precedence, and _single match_ has higher
5151
precedence over _name-based match_.
5252

53+
A usage of a fixed-arity extractor is irrefutable if one of the following condition holds:
54+
55+
- `U = true`
56+
- the extractor is used as a product match
57+
- `U = Some[T]` (for Scala2 compatibility)
58+
- `U <: R` and `U <: { def isEmpty: true }`
59+
5360
### Variadic Extractors
5461

5562
Variadic extractors expose the following signature:
@@ -77,6 +84,12 @@ and `S` conforms to one of the two matches above.
7784
The former form of `unapplySeq` has higher priority, and _sequence match_ has higher
7885
precedence over _product-sequence match_.
7986

87+
A usage of a variadic extractor is irrefutable if one of the following condition holds:
88+
89+
- the extractor is used directly as a sequence match or product-sequence match
90+
- `U = Some[T]` (for Scala2 compatibility)
91+
- `U <: R` and `U <: { def isEmpty: true }`
92+
8093
## Boolean Match
8194

8295
- `U =:= Boolean`

tests/patmat/irrefutable.check

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
22: Pattern Match Exhaustivity: _: Base
2+
65: Pattern Match Exhaustivity: _: M

tests/patmat/irrefutable.scala

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
sealed trait Base
2+
sealed class A(s: String) extends Base
3+
sealed class B(val n: Int) extends Base
4+
sealed case class C(val s: String, val n: Int) extends Base
5+
6+
// boolean
7+
object ExTrue {
8+
def unapply(b: Base): true = b match {
9+
case _ => true
10+
}
11+
12+
def test(b: Base) = b match {
13+
case ExTrue() =>
14+
}
15+
}
16+
17+
object ExFalse {
18+
def unapply(b: Base): Boolean = b match {
19+
case _ => true
20+
}
21+
22+
def test(b: Base) = b match { // warning
23+
case ExFalse() =>
24+
}
25+
}
26+
27+
// product
28+
29+
object ExProduct {
30+
def unapply(b: B): (Int, Int) = (b.n, b.n * b.n)
31+
def test(b: B) = b match {
32+
case ExProduct(x, xx) =>
33+
}
34+
}
35+
36+
// isEmpty/get
37+
38+
trait Res {
39+
def isEmpty: true = true
40+
def get: Int
41+
}
42+
43+
object ExName {
44+
def unapply(b: B): Res = new Res { def get: Int = b.n }
45+
46+
def test(b: B) = b match {
47+
case ExName(x) =>
48+
}
49+
}
50+
51+
sealed class M(n: Int, s: String) extends Product {
52+
def _1: Int = n
53+
def _2: String = s
54+
def isEmpty: Boolean = s.size > n
55+
def get: M = this
56+
57+
def canEqual(that: Any): Boolean = true
58+
def productArity: Int = 2
59+
def productElement(n: Int): Any = ???
60+
}
61+
62+
object ExM {
63+
def unapply(m: M): M = m
64+
65+
def test(m: M) = m match { // warning
66+
case ExM(s) =>
67+
}
68+
}
69+
70+
// some
71+
object ExSome {
72+
def unapply(b: B): Some[Int] = Some(b.n)
73+
74+
def test(b: B) = b match {
75+
case ExSome(x) =>
76+
}
77+
}
78+
79+
object ExSome2 {
80+
def unapply(c: C): Some[C] = Some(c)
81+
82+
def test(c: C) = c match {
83+
case ExSome2(s, n) =>
84+
}
85+
}

0 commit comments

Comments
 (0)