Skip to content

Commit fa22f1b

Browse files
committed
Fix #6490: Add spec for irrefutable extractors
1 parent 68d09ac commit fa22f1b

File tree

7 files changed

+116
-11
lines changed

7 files changed

+116
-11
lines changed

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

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

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

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -274,12 +274,15 @@ object SpaceEngine {
274274
/** Is the unapply irrefutable?
275275
* @param unapp The unapply function reference
276276
*/
277-
def isIrrefutableUnapply(unapp: tpd.Tree)(implicit ctx: Context): Boolean = {
277+
def isIrrefutableUnapply(unapp: tpd.Tree, patSize: Int)(implicit ctx: Context): Boolean = {
278278
val unappResult = unapp.tpe.widen.finalResultType
279279
unappResult.isRef(defn.SomeClass) ||
280-
unappResult =:= ConstantType(Constant(true)) ||
281-
(unapp.symbol.is(Synthetic) && unapp.symbol.owner.linkedClass.is(Case)) ||
282-
productArity(unappResult) > 0
280+
unappResult <:< ConstantType(Constant(true)) ||
281+
(unapp.symbol.is(Synthetic) && unapp.symbol.owner.linkedClass.is(Case)) || // scala2 compatibility
282+
(patSize != -1 && productArity(unappResult) == patSize) || {
283+
val isEmptyTp = extractorMemberType(unappResult, nme.isEmpty, unapp.sourcePos)
284+
isEmptyTp <:< ConstantType(Constant(true))
285+
}
283286
}
284287
}
285288

@@ -333,13 +336,14 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
333336
else {
334337
val (arity, elemTp, resultTp) = unapplySeqInfo(fun.tpe.widen.finalResultType, fun.sourcePos)
335338
if (elemTp.exists)
336-
Prod(erase(pat.tpe.stripAnnots), fun.tpe, fun.symbol, projectSeq(pats) :: Nil, isIrrefutableUnapply(fun))
339+
Prod(erase(pat.tpe.stripAnnots), fun.tpe, fun.symbol, projectSeq(pats) :: Nil, isIrrefutableUnapply(fun, -1))
337340
else
338-
Prod(erase(pat.tpe.stripAnnots), fun.tpe, fun.symbol, pats.take(arity - 1).map(project) :+ projectSeq(pats.drop(arity - 1)),isIrrefutableUnapply(fun))
341+
Prod(erase(pat.tpe.stripAnnots), fun.tpe, fun.symbol, pats.take(arity - 1).map(project) :+ projectSeq(pats.drop(arity - 1)), full = true)
339342
}
340343
else
341-
Prod(erase(pat.tpe.stripAnnots), erase(fun.tpe), fun.symbol, pats.map(project), isIrrefutableUnapply(fun))
342-
case Typed(expr @ Ident(nme.WILDCARD), tpt) =>
344+
Prod(erase(pat.tpe.stripAnnots), erase(fun.tpe), fun.symbol, pats.map(project), isIrrefutableUnapply(fun, pats.length))
345+
case Typed(pat @ UnApply(_, _, _), _) => project(pat)
346+
case Typed(expr, tpt) =>
343347
Typ(erase(expr.tpe.stripAnnots), true)
344348
case Typed(pat, _) =>
345349
project(pat)

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

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

7777
/** 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
@@ -644,7 +644,7 @@ trait Checking {
644644
recur(pat1, pt)
645645
case UnApply(fn, _, pats) =>
646646
check(pat, pt) &&
647-
(isIrrefutableUnapply(fn) || fail(pat, pt)) && {
647+
(isIrrefutableUnapply(fn, pats.length) || fail(pat, pt)) && {
648648
val argPts = unapplyArgs(fn.tpe.widen.finalResultType, fn, pats, pat.sourcePos)
649649
pats.corresponds(argPts)(recur)
650650
}

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)