Skip to content

Commit 2ea1d87

Browse files
Marc KarassevOlivierBlanvillain
Marc Karassev
authored andcommitted
Add an error message for "unapply invalid return type" error.
Part of #1589.
1 parent 818fdbc commit 2ea1d87

File tree

4 files changed

+99
-6
lines changed

4 files changed

+99
-6
lines changed

compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java

+1
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ public enum ErrorMessageID {
116116
TraitRedefinedFinalMethodFromAnyRefID,
117117
PackageNameAlreadyDefinedID,
118118
UnapplyInvalidNumberOfArgumentsID,
119+
UnapplyInvalidReturnTypeID,
119120
StaticFieldsOnlyAllowedInObjectsID,
120121
CyclicInheritanceID,
121122
BadSymbolicReferenceID,

compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala

+56
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import dotty.tools.dotc.core.Flags._
2525
import dotty.tools.dotc.core.SymDenotations.SymDenotation
2626
import dotty.tools.dotc.typer.ErrorReporting.Errors
2727
import scala.util.control.NonFatal
28+
import StdNames.nme
2829

2930
object messages {
3031

@@ -1919,6 +1920,61 @@ object messages {
19191920
|""".stripMargin
19201921
}
19211922

1923+
case class UnapplyInvalidReturnType(unapplyResult: Type, unapplyName: Symbol#ThisName)(implicit ctx: Context)
1924+
extends Message(UnapplyInvalidReturnTypeID) {
1925+
val kind = "Type Mismatch"
1926+
val addendum =
1927+
if (ctx.scala2Mode && unapplyName == nme.unapplySeq)
1928+
"\nYou might want to try to rewrite the extractor to use `unapply` instead."
1929+
else ""
1930+
val msg = hl"""| ${Red(i"$unapplyResult")} is not a valid result type of an $unapplyName method of an ${Magenta("extractor")}.$addendum"""
1931+
val explanation = if (unapplyName.show == "unapply")
1932+
hl"""
1933+
|To be used as an extractor, an unapply method has to return a type that either:
1934+
| - has members ${Magenta("isEmpty: Boolean")} and ${Magenta("get: S")} (usually an ${Green("Option[S]")})
1935+
| - is a ${Green("Boolean")}
1936+
| - is a ${Green("Product")} (like a ${Magenta("Tuple2[T1, T2]")})
1937+
|
1938+
|class A(val i: Int)
1939+
|
1940+
|object B {
1941+
| def unapply(a: A): ${Green("Option[Int]")} = Some(a.i)
1942+
|}
1943+
|
1944+
|object C {
1945+
| def unapply(a: A): ${Green("Boolean")} = a.i == 2
1946+
|}
1947+
|
1948+
|object D {
1949+
| def unapply(a: A): ${Green("(Int, Int)")} = (a.i, a.i)
1950+
|}
1951+
|
1952+
|object Test {
1953+
| def test(a: A) = a match {
1954+
| ${Magenta("case B(1)")} => 1
1955+
| ${Magenta("case a @ C()")} => 2
1956+
| ${Magenta("case D(3, 3)")} => 3
1957+
| }
1958+
|}
1959+
""".stripMargin
1960+
else
1961+
hl"""
1962+
|To be used as an extractor, an unapplySeq method has to return a type which has members
1963+
|${Magenta("isEmpty: Boolean")} and ${Magenta("get: S")} where ${Magenta("S <: Seq[V]")} (usually an ${Green("Option[Seq[V]]")}):
1964+
|
1965+
|object CharList {
1966+
| def unapplySeq(s: String): ${Green("Option[Seq[Char]")} = Some(s.toList)
1967+
|
1968+
| "example" match {
1969+
| ${Magenta("case CharList(c1, c2, c3, c4, _, _, _)")} =>
1970+
| println(s"$$c1,$$c2,$$c3,$$c4")
1971+
| case _ =>
1972+
| println("Expected *exactly* 7 characters!")
1973+
| }
1974+
|}
1975+
""".stripMargin
1976+
}
1977+
19221978
case class StaticFieldsOnlyAllowedInObjects(member: Symbol)(implicit ctx: Context) extends Message(StaticFieldsOnlyAllowedInObjectsID) {
19231979
val msg: String = hl"${"@static"} $member in ${member.owner} must be defined inside an ${"object"}."
19241980
val kind: String = "Syntax"

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

+2-6
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import TypeApplications._
2828
import reporting.diagnostic.Message
2929
import reporting.trace
3030
import Constants.{Constant, IntTag, LongTag}
31-
import dotty.tools.dotc.reporting.diagnostic.messages.{NotAnExtractor, UnapplyInvalidNumberOfArguments}
31+
import dotty.tools.dotc.reporting.diagnostic.messages.{UnapplyInvalidReturnType, NotAnExtractor, UnapplyInvalidNumberOfArguments}
3232
import Denotations.SingleDenotation
3333
import annotation.constructorOnly
3434

@@ -99,11 +99,7 @@ object Applications {
9999
def getTp = extractorMemberType(unapplyResult, nme.get, pos)
100100

101101
def fail = {
102-
val addendum =
103-
if (ctx.scala2Mode && unapplyName == nme.unapplySeq)
104-
"\nYou might want to try to rewrite the extractor to use `unapply` instead."
105-
else ""
106-
ctx.error(em"$unapplyResult is not a valid result type of an $unapplyName method of an extractor$addendum", pos)
102+
ctx.error(UnapplyInvalidReturnType(unapplyResult, unapplyName), pos)
107103
Nil
108104
}
109105

compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala

+40
Original file line numberDiff line numberDiff line change
@@ -1380,6 +1380,46 @@ class ErrorMessagesTests extends ErrorMessagesTest {
13801380
assertEquals("(class Int, class String)", argTypes.map(_.typeSymbol).mkString("(", ", ", ")"))
13811381
}
13821382

1383+
@Test def unapplyInvalidReturnType =
1384+
checkMessagesAfter("frontend") {
1385+
"""
1386+
|class A(val i: Int)
1387+
|
1388+
|object A {
1389+
| def unapply(a: A): Int = a.i
1390+
| def test(a: A) = a match {
1391+
| case A() => 1
1392+
| }
1393+
|}
1394+
""".stripMargin
1395+
}.expect { (ictx, messages) =>
1396+
implicit val ctx: Context = ictx
1397+
assertMessageCount(1, messages)
1398+
val UnapplyInvalidReturnType(unapplyResult, unapplyName) :: Nil = messages
1399+
assertEquals("Int", unapplyResult.show)
1400+
assertEquals("unapply", unapplyName.show)
1401+
}
1402+
1403+
@Test def unapplySeqInvalidReturnType =
1404+
checkMessagesAfter("frontend") {
1405+
"""
1406+
|class A(val i: Int)
1407+
|
1408+
|object A {
1409+
| def unapplySeq(a: A): Int = a.i
1410+
| def test(a: A) = a match {
1411+
| case A() => 1
1412+
| }
1413+
|}
1414+
""".stripMargin
1415+
}.expect { (ictx, messages) =>
1416+
implicit val ctx: Context = ictx
1417+
assertMessageCount(1, messages)
1418+
val UnapplyInvalidReturnType(unapplyResult, unapplyName) :: Nil = messages
1419+
assertEquals("Int", unapplyResult.show)
1420+
assertEquals("unapplySeq", unapplyName.show)
1421+
}
1422+
13831423
@Test def staticOnlyAllowedInsideObjects =
13841424
checkMessagesAfter(CheckStatic.name) {
13851425
"""

0 commit comments

Comments
 (0)