Skip to content

Commit fd35647

Browse files
wwbakkernmcbjan-pieterthijsnissenRoccoMathijn
committed
Improve error message when using unsupported selector type with named pattern
Co-authored-by: nmcb <[email protected]> Co-authored-by: jan-pieter <[email protected]> Co-authored-by: wwbakker <[email protected]> Co-authored-by: thijsnissen <[email protected]> Co-authored-by: RoccoMathijn <[email protected]>
1 parent b35fa8a commit fd35647

File tree

8 files changed

+64
-33
lines changed

8 files changed

+64
-33
lines changed

compiler/src/dotty/tools/dotc/ast/Desugar.scala

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1719,7 +1719,7 @@ object desugar {
17191719
*/
17201720
def tuple(tree: Tuple, pt: Type)(using Context): Tree =
17211721
var elems = checkWellFormedTupleElems(tree.trees)
1722-
if ctx.mode.is(Mode.Pattern) then elems = adaptPatternArgs(elems, pt)
1722+
if ctx.mode.is(Mode.Pattern) then elems = adaptPatternArgs(elems, pt, tree.srcPos)
17231723
val elemValues = elems.mapConserve(stripNamedArg)
17241724
val tup =
17251725
val arity = elems.length
@@ -1759,25 +1759,31 @@ object desugar {
17591759
* - If `elems` are named pattern elements, rearrange them to match `pt`.
17601760
* This requires all names in `elems` to be also present in `pt`.
17611761
*/
1762-
def adaptPatternArgs(elems: List[Tree], pt: Type)(using Context): List[Tree] =
1762+
def adaptPatternArgs(elems: List[Tree], pt: Type, pos: SrcPos)(using Context): List[Tree] =
17631763

17641764
def reorderedNamedArgs(wildcardSpan: Span): List[untpd.Tree] =
1765-
var selNames = pt.namedTupleElementTypes(false).map(_(0))
1766-
if selNames.isEmpty && pt.classSymbol.is(CaseClass) then
1767-
selNames = pt.classSymbol.caseAccessors.map(_.name.asTermName)
1768-
val nameToIdx = selNames.zipWithIndex.toMap
1769-
val reordered = Array.fill[untpd.Tree](selNames.length):
1770-
untpd.Ident(nme.WILDCARD).withSpan(wildcardSpan)
1771-
for case arg @ NamedArg(name: TermName, _) <- elems do
1772-
nameToIdx.get(name) match
1773-
case Some(idx) =>
1774-
if reordered(idx).isInstanceOf[Ident] then
1775-
reordered(idx) = arg
1776-
else
1777-
report.error(em"Duplicate named pattern", arg.srcPos)
1778-
case _ =>
1779-
report.error(em"No element named `$name` is defined in selector type $pt", arg.srcPos)
1780-
reordered.toList
1765+
val isCaseClass = pt.classSymbol.is(CaseClass) && !defn.isTupleClass(pt.classSymbol)
1766+
if !pt.isNamedTupleType && !isCaseClass then
1767+
report.error(NamedPatternNotApplicable(pt), pos)
1768+
Nil
1769+
else
1770+
var selNames = pt.namedTupleElementTypes(false).map(_(0))
1771+
if selNames.isEmpty && isCaseClass then
1772+
selNames = pt.classSymbol.caseAccessors.map(_.name.asTermName)
1773+
val nameToIdx = selNames.zipWithIndex.toMap
1774+
val reordered = Array.fill[untpd.Tree](selNames.length):
1775+
untpd.Ident(nme.WILDCARD).withSpan(wildcardSpan)
1776+
for case arg@NamedArg(name: TermName, _) <- elems do
1777+
nameToIdx.get(name) match
1778+
case Some(idx) =>
1779+
if reordered(idx).isInstanceOf[Ident] then
1780+
reordered(idx) = arg
1781+
else
1782+
report.error(em"Duplicate named pattern", arg.srcPos)
1783+
case _ =>
1784+
report.error(em"No element named `$name` is defined in selector type $pt", arg.srcPos)
1785+
reordered.toList
1786+
end if
17811787

17821788
elems match
17831789
case (first @ NamedArg(_, _)) :: _ => reorderedNamedArgs(first.span.startPos)

compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe
228228
case OnlyFullyDependentAppliedConstructorTypeID // errorNumber: 212
229229
case PointlessAppliedConstructorTypeID // errorNumber: 213
230230
case IllegalContextBoundsID // errorNumber: 214
231+
case NamedPatternNotApplicableID // errorNumber: 215
231232

232233
def errorNumber = ordinal - 1
233234

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3534,4 +3534,8 @@ final class IllegalContextBounds(using Context) extends SyntaxMsg(IllegalContext
35343534

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

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

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,10 +207,10 @@ object Applications {
207207
case List(defn.NamedTuple(_, _))=>
208208
// if the product types list is a singleton named tuple, autotupling might be applied, so don't fail eagerly
209209
tryEither[Option[List[untpd.Tree]]]
210-
(Some(desugar.adaptPatternArgs(elems, pt)))
210+
(Some(desugar.adaptPatternArgs(elems, pt, pos)))
211211
((_, _) => None)
212212
case pts =>
213-
Some(desugar.adaptPatternArgs(elems, pt))
213+
Some(desugar.adaptPatternArgs(elems, pt, pos))
214214

215215
private def getUnapplySelectors(tp: Type)(using Context): List[Type] =
216216
// We treat patterns as product elements if

tests/neg/i22903.check

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
-- [E215] Pattern Match Error: tests/neg/i22903.scala:18:21 ------------------------------------------------------------
2+
18 | case ProductMatch(someName = x) => println (x) // error
3+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
4+
| Named patterns cannot be used with CustomProduct, because it is not a named tuple or case class

tests/neg/i22903.scala

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
class CustomProduct(x: Int) extends Product {
2+
def _1 = someName
3+
def _2 = blub
4+
5+
val someName = x + 5
6+
val blub = "blub"
7+
8+
override def canEqual(that: Any): Boolean = ???
9+
}
10+
11+
object ProductMatch {
12+
def unapply(x: Int): CustomProduct = new CustomProduct(x)
13+
}
14+
15+
@main
16+
def run = {
17+
3 match {
18+
case ProductMatch(someName = x) => println (x) // error
19+
}
20+
}

tests/neg/named-tuples.check

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,10 @@
4747
26 | val (name = x, agee = y) = person // error
4848
| ^^^^^^^^
4949
| No element named `agee` is defined in selector type (name : String, age : Int)
50-
-- Error: tests/neg/named-tuples.scala:29:10 ---------------------------------------------------------------------------
51-
29 | case (name = n, age = a) => () // error // error
52-
| ^^^^^^^^
53-
| No element named `name` is defined in selector type (String, Int)
54-
-- Error: tests/neg/named-tuples.scala:29:20 ---------------------------------------------------------------------------
55-
29 | case (name = n, age = a) => () // error // error
56-
| ^^^^^^^
57-
| No element named `age` is defined in selector type (String, Int)
50+
-- [E215] Pattern Match Error: tests/neg/named-tuples.scala:29:9 -------------------------------------------------------
51+
29 | case (name = n, age = a) => () // error
52+
| ^^^^^^^^^^^^^^^^^^^
53+
| Named patterns cannot be used with (String, Int), because it is not a named tuple or case class
5854
-- [E172] Type Error: tests/neg/named-tuples.scala:31:27 ---------------------------------------------------------------
5955
31 | val pp = person ++ (1, 2) // error
6056
| ^
@@ -75,10 +71,10 @@
7571
41 | case (name, age = a) => () // error
7672
| ^^^^^^^
7773
| Illegal combination of named and unnamed tuple elements
78-
-- Error: tests/neg/named-tuples.scala:44:10 ---------------------------------------------------------------------------
74+
-- [E215] Pattern Match Error: tests/neg/named-tuples.scala:44:9 -------------------------------------------------------
7975
44 | case (age = x) => // error
80-
| ^^^^^^^
81-
| No element named `age` is defined in selector type Tuple
76+
| ^^^^^^^^^
77+
| Named patterns cannot be used with Tuple, because it is not a named tuple or case class
8278
-- [E172] Type Error: tests/neg/named-tuples.scala:46:27 ---------------------------------------------------------------
8379
46 | val p2 = person ++ person // error
8480
| ^

tests/neg/named-tuples.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ object Test:
2626
val (name = x, agee = y) = person // error
2727

2828
("Ives", 2) match
29-
case (name = n, age = a) => () // error // error
29+
case (name = n, age = a) => () // error
3030

3131
val pp = person ++ (1, 2) // error
3232
val qq = ("a", true) ++ (1, 2)

0 commit comments

Comments
 (0)