Skip to content

Commit def46a9

Browse files
committed
SI-7850 CCE in patmat with invalid isEmpty.
Name-based pattern matcher needed some hardening against unapply methods with the right name but wrong types. Only isEmpty methods which return Boolean are acceptable. Catching it directly rather than indirectly also allowed for better error messages.
1 parent 11bfa25 commit def46a9

File tree

8 files changed

+53
-18
lines changed

8 files changed

+53
-18
lines changed

src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -625,8 +625,7 @@ trait ContextErrors {
625625
setError(tree)
626626
}
627627

628-
def CaseClassConstructorError(tree: Tree) = {
629-
val baseMessage = tree.symbol + " is not a case class constructor, nor does it have an unapply/unapplySeq method"
628+
def CaseClassConstructorError(tree: Tree, baseMessage: String) = {
630629
val addendum = directUnapplyMember(tree.symbol.info) match {
631630
case sym if hasMultipleNonImplicitParamLists(sym) => s"\nNote: ${sym.defString} exists in ${tree.symbol}, but it cannot be used as an extractor due to its second non-implicit parameter list"
632631
case _ => ""

src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,18 +78,32 @@ trait PatternTypers {
7878
// Do some ad-hoc overloading resolution and update the tree's symbol and type
7979
// do not update the symbol if the tree's symbol's type does not define an unapply member
8080
// (e.g. since it's some method that returns an object with an unapply member)
81-
val fun = inPlaceAdHocOverloadingResolution(fun0)(hasUnapplyMember)
82-
def caseClass = fun.tpe.typeSymbol.linkedClassOfClass
81+
val fun = inPlaceAdHocOverloadingResolution(fun0)(hasUnapplyMember)
82+
val caseClass = fun.tpe.typeSymbol.linkedClassOfClass
83+
val member = unapplyMember(fun.tpe)
84+
def resultType = (fun.tpe memberType member).finalResultType
85+
def isEmptyType = resultOfMatchingMethod(resultType, nme.isEmpty)()
86+
def isOkay = (
87+
resultType.isErroneous
88+
|| (resultType <:< BooleanTpe)
89+
|| (isEmptyType <:< BooleanTpe)
90+
|| member.isMacro
91+
|| member.isOverloaded // the whole overloading situation is over the rails
92+
)
8393

8494
// Dueling test cases: pos/overloaded-unapply.scala, run/case-class-23.scala, pos/t5022.scala
8595
// A case class with 23+ params has no unapply method.
8696
// A case class constructor may be overloaded with unapply methods in the companion.
87-
if (caseClass.isCase && !unapplyMember(fun.tpe).isOverloaded)
97+
if (caseClass.isCase && !member.isOverloaded)
8898
logResult(s"convertToCaseConstructor($fun, $caseClass, pt=$pt)")(convertToCaseConstructor(fun, caseClass, pt))
89-
else if (hasUnapplyMember(fun))
99+
else if (!reallyExists(member))
100+
CaseClassConstructorError(fun, s"${fun.symbol} is not a case class, nor does it have an unapply/unapplySeq member")
101+
else if (isOkay)
90102
fun
103+
else if (isEmptyType == NoType)
104+
CaseClassConstructorError(fun, s"an unapply result must have a member `def isEmpty: Boolean")
91105
else
92-
CaseClassConstructorError(fun)
106+
CaseClassConstructorError(fun, s"an unapply result must have a member `def isEmpty: Boolean (found: def isEmpty: $isEmptyType)")
93107
}
94108

95109
def typedArgsForFormals(args: List[Tree], formals: List[Type], mode: Mode): List[Tree] = {

src/compiler/scala/tools/nsc/typechecker/Unapplies.scala

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,6 @@ trait Unapplies extends ast.TreeDSL {
3939
*/
4040
def unapplyMember(tp: Type): Symbol = directUnapplyMember(tp) filter (sym => !hasMultipleNonImplicitParamLists(sym))
4141

42-
def unapplyMemberType(tree: Tree): Type = tree.tpe memberType unapplyMember(tree.tpe)
43-
4442
object HasUnapply {
4543
def unapply(tp: Type): Option[Symbol] = unapplyMember(tp).toOption
4644
}

src/reflect/scala/reflect/internal/StdNames.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -691,6 +691,7 @@ trait StdNames {
691691
val inlinedEquals: NameType = "inlinedEquals"
692692
val isArray: NameType = "isArray"
693693
val isDefinedAt: NameType = "isDefinedAt"
694+
val isEmpty: NameType = "isEmpty"
694695
val isInstanceOf_ : NameType = "isInstanceOf"
695696
val isInstanceOf_Ob : NameType = "$isInstanceOf"
696697
val java: NameType = "java"

test/files/neg/t4425.check

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
t4425.scala:3: error: object X is not a case class constructor, nor does it have an unapply/unapplySeq method
1+
t4425.scala:3: error: object X is not a case class, nor does it have an unapply/unapplySeq member
22
Note: def unapply(x: Int)(y: Option[Int]): None.type exists in object X, but it cannot be used as an extractor due to its second non-implicit parameter list
33
42 match { case _ X _ => () }
44
^
5-
t4425.scala:8: error: object X is not a case class constructor, nor does it have an unapply/unapplySeq method
5+
t4425.scala:8: error: object X is not a case class, nor does it have an unapply/unapplySeq member
66
Note: def unapply(x: Int)(y: Int): Some[(Int, Int)] exists in object X, but it cannot be used as an extractor due to its second non-implicit parameter list
77
42 match { case _ X _ => () }
88
^
9-
t4425.scala:13: error: object X is not a case class constructor, nor does it have an unapply/unapplySeq method
9+
t4425.scala:13: error: object X is not a case class, nor does it have an unapply/unapplySeq member
1010
Note: def unapply(x: String)(y: String): Some[(Int, Int)] exists in object X, but it cannot be used as an extractor due to its second non-implicit parameter list
1111
"" match { case _ X _ => () }
1212
^

test/files/neg/t4425b.check

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
1-
t4425b.scala:5: error: object X is not a case class constructor, nor does it have an unapply/unapplySeq method
1+
t4425b.scala:5: error: object X is not a case class, nor does it have an unapply/unapplySeq member
22
Note: def unapply(x: String)(y: String): Nothing exists in object X, but it cannot be used as an extractor due to its second non-implicit parameter list
33
println( "" match { case _ X _ => "ok" ; case _ => "fail" })
44
^
5-
t4425b.scala:6: error: object X is not a case class constructor, nor does it have an unapply/unapplySeq method
5+
t4425b.scala:6: error: object X is not a case class, nor does it have an unapply/unapplySeq member
66
Note: def unapply(x: String)(y: String): Nothing exists in object X, but it cannot be used as an extractor due to its second non-implicit parameter list
77
println((X: Any) match { case _ X _ => "ok" ; case _ => "fail" })
88
^
9-
t4425b.scala:7: error: object X is not a case class constructor, nor does it have an unapply/unapplySeq method
9+
t4425b.scala:7: error: object X is not a case class, nor does it have an unapply/unapplySeq member
1010
Note: def unapply(x: String)(y: String): Nothing exists in object X, but it cannot be used as an extractor due to its second non-implicit parameter list
1111
println( "" match { case X(_) => "ok" ; case _ => "fail" })
1212
^
13-
t4425b.scala:8: error: object X is not a case class constructor, nor does it have an unapply/unapplySeq method
13+
t4425b.scala:8: error: object X is not a case class, nor does it have an unapply/unapplySeq member
1414
Note: def unapply(x: String)(y: String): Nothing exists in object X, but it cannot be used as an extractor due to its second non-implicit parameter list
1515
println((X: Any) match { case X(_) => "ok" ; case _ => "fail" })
1616
^
17-
t4425b.scala:9: error: object X is not a case class constructor, nor does it have an unapply/unapplySeq method
17+
t4425b.scala:9: error: object X is not a case class, nor does it have an unapply/unapplySeq member
1818
Note: def unapply(x: String)(y: String): Nothing exists in object X, but it cannot be used as an extractor due to its second non-implicit parameter list
1919
println( "" match { case X(_, _) => "ok" ; case _ => "fail" })
2020
^
21-
t4425b.scala:10: error: object X is not a case class constructor, nor does it have an unapply/unapplySeq method
21+
t4425b.scala:10: error: object X is not a case class, nor does it have an unapply/unapplySeq member
2222
Note: def unapply(x: String)(y: String): Nothing exists in object X, but it cannot be used as an extractor due to its second non-implicit parameter list
2323
println((X: Any) match { case X(_, _) => "ok" ; case _ => "fail" })
2424
^

test/files/neg/t7850.check

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
t7850.scala:11: error: an unapply result must have a member `def isEmpty: Boolean (found: def isEmpty: Casey)
2+
val Casey(x1) = new Casey(1)
3+
^
4+
t7850.scala:12: error: an unapply result must have a member `def isEmpty: Boolean
5+
val Dingy(x2) = new Dingy(1)
6+
^
7+
two errors found

test/files/neg/t7850.scala

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// isEmpty returns non-boolean
2+
class Casey(a: Int) { def isEmpty = this; def get = this }
3+
object Casey { def unapply(a: Casey) = a }
4+
5+
// no isEmpty method at all
6+
class Dingy(a: Int) { def get = this }
7+
object Dingy { def unapply(a: Dingy) = a }
8+
9+
object Test {
10+
def main(args: Array[String]) {
11+
val Casey(x1) = new Casey(1)
12+
val Dingy(x2) = new Dingy(1)
13+
println(s"$x1 $x2")
14+
}
15+
}
16+

0 commit comments

Comments
 (0)