Skip to content

Commit e127bb4

Browse files
committed
Fix treatment of UnApply pattern definitions
1 parent 21a5bbe commit e127bb4

File tree

4 files changed

+47
-27
lines changed

4 files changed

+47
-27
lines changed

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

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -280,11 +280,25 @@ trait SpaceLogic {
280280
}
281281
}
282282

283+
object SpaceEngine {
284+
285+
/** Is the unapply irrefutable?
286+
* @param unapp The unapply function reference
287+
*/
288+
def isIrrefutableUnapply(unapp: tpd.Tree)(implicit ctx: Context): Boolean = {
289+
val unappResult = unapp.tpe.widen.finalResultType
290+
unappResult.isRef(defn.SomeClass) ||
291+
unappResult =:= ConstantType(Constant(true)) ||
292+
(unapp.symbol.is(Synthetic) && unapp.symbol.owner.linkedClass.is(Case)) ||
293+
productArity(unappResult) > 0
294+
}
295+
}
296+
283297
/** Scala implementation of space logic */
284298
class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
285299
import tpd._
300+
import SpaceEngine._
286301

287-
private val scalaSomeClass = ctx.requiredClass("scala.Some")
288302
private val scalaSeqFactoryClass = ctx.requiredClass("scala.collection.generic.SeqFactory")
289303
private val scalaListType = ctx.requiredClassRef("scala.collection.immutable.List")
290304
private val scalaNilType = ctx.requiredModuleRef("scala.collection.immutable.Nil")
@@ -309,15 +323,6 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
309323
else Typ(AndType(tp1, tp2), true)
310324
}
311325

312-
/** Whether the extractor is irrefutable */
313-
def irrefutable(unapp: Tree): Boolean = {
314-
// TODO: optionless patmat
315-
unapp.tpe.widen.finalResultType.isRef(scalaSomeClass) ||
316-
unapp.tpe.widen.finalResultType =:= ConstantType(Constant(true)) ||
317-
(unapp.symbol.is(Synthetic) && unapp.symbol.owner.linkedClass.is(Case)) ||
318-
productArity(unapp.tpe.widen.finalResultType) > 0
319-
}
320-
321326
/** Return the space that represents the pattern `pat` */
322327
def project(pat: Tree): Space = pat match {
323328
case Literal(c) =>
@@ -340,12 +345,12 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
340345
else {
341346
val (arity, elemTp, resultTp) = unapplySeqInfo(fun.tpe.widen.finalResultType, fun.sourcePos)
342347
if (elemTp.exists)
343-
Prod(erase(pat.tpe.stripAnnots), fun.tpe, fun.symbol, projectSeq(pats) :: Nil, irrefutable(fun))
348+
Prod(erase(pat.tpe.stripAnnots), fun.tpe, fun.symbol, projectSeq(pats) :: Nil, isIrrefutableUnapply(fun))
344349
else
345-
Prod(erase(pat.tpe.stripAnnots), fun.tpe, fun.symbol, pats.take(arity - 1).map(project) :+ projectSeq(pats.drop(arity - 1)), irrefutable(fun))
350+
Prod(erase(pat.tpe.stripAnnots), fun.tpe, fun.symbol, pats.take(arity - 1).map(project) :+ projectSeq(pats.drop(arity - 1)),isIrrefutableUnapply(fun))
346351
}
347352
else
348-
Prod(erase(pat.tpe.stripAnnots), fun.tpe, fun.symbol, pats.map(project), irrefutable(fun))
353+
Prod(erase(pat.tpe.stripAnnots), fun.tpe, fun.symbol, pats.map(project), isIrrefutableUnapply(fun))
349354
case Typed(pat @ UnApply(_, _, _), _) => project(pat)
350355
case Typed(expr, tpt) =>
351356
Typ(erase(expr.tpe.stripAnnots), true)

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

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import ErrorReporting.{err, errorType}
2626
import config.Printers.{typr, patmatch}
2727
import NameKinds.DefaultGetterName
2828
import Applications.unapplyArgs
29+
import transform.patmat.SpaceEngine.isIrrefutableUnapply
2930

3031
import collection.mutable
3132
import SymDenotations.{NoCompleter, NoDenotation}
@@ -604,27 +605,26 @@ trait Checking {
604605
def checkIrrefutable(pat: Tree, pt: Type)(implicit ctx: Context): Boolean = {
605606
patmatch.println(i"check irrefutable $pat: ${pat.tpe} against $pt")
606607

607-
def check(pat: Tree, pt: Type): Boolean = {
608-
if (pt <:< pat.tpe)
609-
true
610-
else {
611-
ctx.errorOrMigrationWarning(
612-
ex"""pattern's type ${pat.tpe} is more specialized than the right hand side expression's type ${pt.dropAnnot(defn.UncheckedAnnot)}
613-
|
614-
|If the narrowing is intentional, this can be communicated by writing `: @unchecked` after the full pattern.${err.rewriteNotice}""",
615-
pat.sourcePos)
616-
false
617-
}
608+
def fail(pat: Tree, pt: Type): Boolean = {
609+
ctx.errorOrMigrationWarning(
610+
ex"""pattern's type ${pat.tpe} is more specialized than the right hand side expression's type ${pt.dropAnnot(defn.UncheckedAnnot)}
611+
|
612+
|If the narrowing is intentional, this can be communicated by writing `: @unchecked` after the full pattern.${err.rewriteNotice}""",
613+
pat.sourcePos)
614+
false
618615
}
619616

617+
def check(pat: Tree, pt: Type): Boolean = (pt <:< pat.tpe) || fail(pat, pt)
618+
620619
!ctx.settings.strict.value || // only in -strict mode for now since mitigations work only after this PR
621620
pat.tpe.widen.hasAnnotation(defn.UncheckedAnnot) || {
622621
pat match {
623622
case Bind(_, pat1) =>
624623
checkIrrefutable(pat1, pt)
625624
case UnApply(fn, _, pats) =>
626-
check(pat, pt) && {
627-
val argPts = unapplyArgs(fn.tpe.finalResultType, fn, pats, pat.sourcePos)
625+
check(pat, pt) &&
626+
(isIrrefutableUnapply(fn) || fail(pat, pt)) && {
627+
val argPts = unapplyArgs(fn.tpe.widen.finalResultType, fn, pats, pat.sourcePos)
628628
pats.corresponds(argPts)(checkIrrefutable)
629629
}
630630
case Alternative(pats) =>

tests/neg-strict/unchecked-patterns.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
object Test {
2+
23
val (y1: Some[Int] @unchecked) = Some(1): Option[Int] // OK
34
val y2: Some[Int] @unchecked = Some(1): Option[Int] // error
45

@@ -7,4 +8,16 @@ object Test {
78
val 1 *: cs = 1 *: () // error
89

910
val (_: Int | _: Any) = ??? : Any // error
11+
12+
object Positive { def unapply(i: Int): Option[Int] = Some(i).filter(_ > 0) }
13+
object Always1 { def unapply(i: Int): Some[Int] = Some(i) }
14+
object Pair { def unapply(t: (Int, Int)): t.type = t }
15+
object Triple { def unapply(t: (Int, Int, Int)): (Int, Int, Int) = t }
16+
17+
val Positive(p) = 5 // error
18+
val Some(s1) = Option(1) // error
19+
val Some(s2) = Some(1) // OK
20+
val Always1(p1) = 5 // OK
21+
val Pair(t1, t2) = (5, 5) // OK
22+
val Triple(u1, u2, u3) = (5, 5, 5) // OK
1023
}

tests/run/unchecked-patterns.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,7 @@ object Test extends App {
55
val a :: as: @unchecked = List(1, 2, 3)
66
val lst @ b :: bs: @unchecked = List(1, 2, 3)
77
val (1, c): @unchecked = (1, 2)
8-
val 1 *: cs: @unchecked = 1 *: () // error
8+
9+
object Positive { def unapply(i: Int): Option[Int] = Some(i).filter(_ > 0) }
10+
val Positive(p): @unchecked = 5
911
}

0 commit comments

Comments
 (0)