Skip to content

Unhelpful error message when trying to use named extraction, when not matching case class or named tuple #23354

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 24 additions & 18 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1719,7 +1719,7 @@ object desugar {
*/
def tuple(tree: Tuple, pt: Type)(using Context): Tree =
var elems = checkWellFormedTupleElems(tree.trees)
if ctx.mode.is(Mode.Pattern) then elems = adaptPatternArgs(elems, pt)
if ctx.mode.is(Mode.Pattern) then elems = adaptPatternArgs(elems, pt, tree.srcPos)
val elemValues = elems.mapConserve(stripNamedArg)
val tup =
val arity = elems.length
Expand Down Expand Up @@ -1759,25 +1759,31 @@ object desugar {
* - If `elems` are named pattern elements, rearrange them to match `pt`.
* This requires all names in `elems` to be also present in `pt`.
*/
def adaptPatternArgs(elems: List[Tree], pt: Type)(using Context): List[Tree] =
def adaptPatternArgs(elems: List[Tree], pt: Type, pos: SrcPos)(using Context): List[Tree] =

def reorderedNamedArgs(wildcardSpan: Span): List[untpd.Tree] =
var selNames = pt.namedTupleElementTypes(false).map(_(0))
if selNames.isEmpty && pt.classSymbol.is(CaseClass) then
selNames = pt.classSymbol.caseAccessors.map(_.name.asTermName)
val nameToIdx = selNames.zipWithIndex.toMap
val reordered = Array.fill[untpd.Tree](selNames.length):
untpd.Ident(nme.WILDCARD).withSpan(wildcardSpan)
for case arg @ NamedArg(name: TermName, _) <- elems do
nameToIdx.get(name) match
case Some(idx) =>
if reordered(idx).isInstanceOf[Ident] then
reordered(idx) = arg
else
report.error(em"Duplicate named pattern", arg.srcPos)
case _ =>
report.error(em"No element named `$name` is defined in selector type $pt", arg.srcPos)
reordered.toList
val isCaseClass = pt.classSymbol.is(CaseClass) && !defn.isTupleClass(pt.classSymbol)
if !pt.isNamedTupleType && !isCaseClass then
report.error(NamedPatternNotApplicable(pt), pos)
Nil
else
var selNames = pt.namedTupleElementTypes(false).map(_(0))
if selNames.isEmpty && isCaseClass then
selNames = pt.classSymbol.caseAccessors.map(_.name.asTermName)
val nameToIdx = selNames.zipWithIndex.toMap
val reordered = Array.fill[untpd.Tree](selNames.length):
untpd.Ident(nme.WILDCARD).withSpan(wildcardSpan)
for case arg@NamedArg(name: TermName, _) <- elems do
nameToIdx.get(name) match
case Some(idx) =>
if reordered(idx).isInstanceOf[Ident] then
reordered(idx) = arg
else
report.error(em"Duplicate named pattern", arg.srcPos)
case _ =>
report.error(em"No element named `$name` is defined in selector type $pt", arg.srcPos)
reordered.toList
end if

elems match
case (first @ NamedArg(_, _)) :: _ => reorderedNamedArgs(first.span.startPos)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe
case OnlyFullyDependentAppliedConstructorTypeID // errorNumber: 212
case PointlessAppliedConstructorTypeID // errorNumber: 213
case IllegalContextBoundsID // errorNumber: 214
case NamedPatternNotApplicableID // errorNumber: 215

def errorNumber = ordinal - 1

Expand Down
6 changes: 5 additions & 1 deletion compiler/src/dotty/tools/dotc/reporting/messages.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3534,4 +3534,8 @@ final class IllegalContextBounds(using Context) extends SyntaxMsg(IllegalContext

override protected def explain(using Context): String = ""

end IllegalContextBounds
final class NamedPatternNotApplicable(selectorType: Type)(using Context) extends PatternMatchMsg(NamedPatternNotApplicableID):
override protected def msg(using Context): String =
i"Named patterns cannot be used with $selectorType, because it is not a named tuple or case class"

override protected def explain(using Context): String = ""
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -207,10 +207,10 @@ object Applications {
case List(defn.NamedTuple(_, _))=>
// if the product types list is a singleton named tuple, autotupling might be applied, so don't fail eagerly
tryEither[Option[List[untpd.Tree]]]
(Some(desugar.adaptPatternArgs(elems, pt)))
(Some(desugar.adaptPatternArgs(elems, pt, pos)))
((_, _) => None)
case pts =>
Some(desugar.adaptPatternArgs(elems, pt))
Some(desugar.adaptPatternArgs(elems, pt, pos))

private def getUnapplySelectors(tp: Type)(using Context): List[Type] =
// We treat patterns as product elements if
Expand Down
4 changes: 4 additions & 0 deletions tests/neg/i22903.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-- [E215] Pattern Match Error: tests/neg/i22903.scala:18:21 ------------------------------------------------------------
18 | case ProductMatch(someName = x) => println (x) // error
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
| Named patterns cannot be used with CustomProduct, because it is not a named tuple or case class
20 changes: 20 additions & 0 deletions tests/neg/i22903.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
class CustomProduct(x: Int) extends Product {
def _1 = someName
def _2 = blub

val someName = x + 5
val blub = "blub"

override def canEqual(that: Any): Boolean = ???
}

object ProductMatch {
def unapply(x: Int): CustomProduct = new CustomProduct(x)
}

@main
def run = {
3 match {
case ProductMatch(someName = x) => println (x) // error
}
}
18 changes: 7 additions & 11 deletions tests/neg/named-tuples.check
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,10 @@
26 | val (name = x, agee = y) = person // error
| ^^^^^^^^
| No element named `agee` is defined in selector type (name : String, age : Int)
-- Error: tests/neg/named-tuples.scala:29:10 ---------------------------------------------------------------------------
29 | case (name = n, age = a) => () // error // error
| ^^^^^^^^
| No element named `name` is defined in selector type (String, Int)
-- Error: tests/neg/named-tuples.scala:29:20 ---------------------------------------------------------------------------
29 | case (name = n, age = a) => () // error // error
| ^^^^^^^
| No element named `age` is defined in selector type (String, Int)
-- [E215] Pattern Match Error: tests/neg/named-tuples.scala:29:9 -------------------------------------------------------
29 | case (name = n, age = a) => () // error
| ^^^^^^^^^^^^^^^^^^^
| Named patterns cannot be used with (String, Int), because it is not a named tuple or case class
-- [E172] Type Error: tests/neg/named-tuples.scala:31:27 ---------------------------------------------------------------
31 | val pp = person ++ (1, 2) // error
| ^
Expand All @@ -75,10 +71,10 @@
41 | case (name, age = a) => () // error
| ^^^^^^^
| Illegal combination of named and unnamed tuple elements
-- Error: tests/neg/named-tuples.scala:44:10 ---------------------------------------------------------------------------
-- [E215] Pattern Match Error: tests/neg/named-tuples.scala:44:9 -------------------------------------------------------
44 | case (age = x) => // error
| ^^^^^^^
| No element named `age` is defined in selector type Tuple
| ^^^^^^^^^
| Named patterns cannot be used with Tuple, because it is not a named tuple or case class
-- [E172] Type Error: tests/neg/named-tuples.scala:46:27 ---------------------------------------------------------------
46 | val p2 = person ++ person // error
| ^
Expand Down
2 changes: 1 addition & 1 deletion tests/neg/named-tuples.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ object Test:
val (name = x, agee = y) = person // error

("Ives", 2) match
case (name = n, age = a) => () // error // error
case (name = n, age = a) => () // error

val pp = person ++ (1, 2) // error
val qq = ("a", true) ++ (1, 2)
Expand Down
Loading