diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 98eb8f895f5b..d71a6329e8b0 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -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 @@ -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 + inline def isCaseClass = pt.classSymbol.is(CaseClass) && !defn.isTupleClass(pt.classSymbol) + if !isCaseClass && !pt.isNamedTupleType then + report.error(NamedPatternNotApplicable(pt), pos) + Nil + else + var selNames = pt.namedTupleElementTypes(false).map(_(0)) + if isCaseClass && selNames.isEmpty 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) diff --git a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala index ecde64a720aa..96942f913934 100644 --- a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala +++ b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala @@ -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 diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 5faec1fafcdf..67feb3945b07 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -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 = "" diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index bf9ebf7b9926..0f7151c9bf8b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -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 diff --git a/tests/neg/i22903.check b/tests/neg/i22903.check new file mode 100644 index 000000000000..7a60d43b5c1d --- /dev/null +++ b/tests/neg/i22903.check @@ -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 diff --git a/tests/neg/i22903.scala b/tests/neg/i22903.scala new file mode 100644 index 000000000000..255a6aeb6c07 --- /dev/null +++ b/tests/neg/i22903.scala @@ -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 + } +} \ No newline at end of file diff --git a/tests/neg/named-tuples.check b/tests/neg/named-tuples.check index 8ec958b6a75d..68ee61355107 100644 --- a/tests/neg/named-tuples.check +++ b/tests/neg/named-tuples.check @@ -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 | ^ @@ -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 | ^ diff --git a/tests/neg/named-tuples.scala b/tests/neg/named-tuples.scala index daae6e26bac2..2d8e0663dee0 100644 --- a/tests/neg/named-tuples.scala +++ b/tests/neg/named-tuples.scala @@ -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)