diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java b/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java index d99d5014a281..99311fd1cfc1 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java @@ -133,7 +133,8 @@ public enum ErrorMessageID { TypeTestAlwaysSucceedsID, TermMemberNeedsNeedsResultTypeForImplicitSearchID, CaseClassCannotExtendEnumID, - ValueClassParameterMayNotBeCallByNameID + ValueClassParameterMayNotBeCallByNameID, + NotAnExtractorID ; public int errorNumber() { diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala index cec649bc061b..10ac5e7356ea 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -2117,4 +2117,18 @@ object messages { override def msg: String = hl"""normal case class cannot extend an enum. case $cls in ${cls.owner} is extending enum ${parent.name}.""" override def explanation: String = "" } + + case class NotAnExtractor(tree: untpd.Tree)(implicit ctx: Context) extends Message(NotAnExtractorID) { + override def msg: String = hl"$tree cannot be used as an extractor in a pattern because it lacks an unapply or unapplySeq method" + override def kind: String = "Syntax" + override def explanation: String = + hl"""|An `unapply` method should be defined in an `object` as follow: + | - If it is just a test, return a `Boolean`. For example `case even()` + | - If it returns a single sub-value of type T, return an `Option[T]` + | - If it returns several sub-values T1,...,Tn, group them in an optional tuple `Option[(T1,...,Tn)]` + | + |Sometimes, the number of sub-values isn’t fixed and we would like to return a sequence. + |For this reason, you can also define patterns through `unapplySeq` which returns `Option[Seq[T]]`. + |This mechanism is used for instance in pattern `case List(x1, ..., xn)`""".stripMargin + } } diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 4de90b309307..24e5d8dfc796 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -32,7 +32,7 @@ import language.implicitConversions import reporting.diagnostic.Message import reporting.trace import Constants.{Constant, IntTag, LongTag} -import dotty.tools.dotc.reporting.diagnostic.messages.UnapplyInvalidNumberOfArguments +import dotty.tools.dotc.reporting.diagnostic.messages.{NotAnExtractor, UnapplyInvalidNumberOfArguments} import scala.collection.mutable.ListBuffer @@ -895,8 +895,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => def typedUnApply(tree: untpd.Apply, selType: Type)(implicit ctx: Context): Tree = track("typedUnApply") { val Apply(qual, args) = tree - def notAnExtractor(tree: Tree) = - errorTree(tree, s"${qual.show} cannot be used as an extractor in a pattern because it lacks an unapply or unapplySeq method") + def notAnExtractor(tree: Tree) = errorTree(tree, NotAnExtractor(qual)) /** If this is a term ref tree, try to typecheck with its type name. * If this refers to a type alias, follow the alias, and if diff --git a/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala b/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala index b176712f76f5..78b52cdb1a11 100644 --- a/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala +++ b/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala @@ -1511,4 +1511,20 @@ class ErrorMessagesTests extends ErrorMessagesTest { assertEquals(tailRegMessages, Set("variable", "value", "object", "class")) } + @Test def notAnExtractor() = + checkMessagesAfter(FrontEnd.name) { + """ + | class Foo + | object Test { + | def test(foo: Foo) = foo match { + | case Foo(name) => ??? + | } + | } + """.stripMargin + }.expect { (ictx, messages) => + implicit val ctx: Context = ictx + assertMessageCount(1, messages) + val NotAnExtractor(tree) = messages.head + assertEquals("Foo", tree.show) + } }