Skip to content

Commit

Permalink
Validate named patterns for case classes (#22242)
Browse files Browse the repository at this point in the history
I found out that there is no validation happening for named patterns of
case classes.

https://scastie.scala-lang.org/W4p7RBrySwuteISEPuqSUw

There were 2 different things that blocked the errors:
1. We actually did not run `checkWellFormedTupleElems` in that scenario,
2. We run `tryAdaptPatternArgs` in `tryEither` which has nested context
that does not report errors which are not sticky.
  • Loading branch information
rochala authored Jan 14, 2025
1 parent ad90f14 commit 1bcc03c
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 10 deletions.
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1667,7 +1667,7 @@ object desugar {
AppliedTypeTree(
TypeTree(defn.throwsAlias.typeRef).withSpan(op.span), tpt :: excepts :: Nil)

private def checkWellFormedTupleElems(elems: List[Tree])(using Context): List[Tree] =
def checkWellFormedTupleElems(elems: List[Tree])(using Context): List[Tree] =
val seen = mutable.Set[Name]()
for case arg @ NamedArg(name, _) <- elems do
if seen.contains(name) then
Expand Down
20 changes: 11 additions & 9 deletions compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -208,20 +208,22 @@ object Applications {
else tp :: Nil

private def productUnapplySelectors(tp: Type)(using Context): Option[List[Type]] =
val validatedTupleElements = desugar.checkWellFormedTupleElems(args)

if defn.isProductSubType(tp) && args.lengthCompare(productArity(tp)) <= 0 then
tryAdaptPatternArgs(args, tp) match
tryAdaptPatternArgs(validatedTupleElements, tp) match
case Some(args1) if isProductMatch(tp, args1.length, pos) =>
args = args1
Some(productSelectorTypes(tp, pos))
case _ => None
else tp.widen.normalized.dealias match
case tp @ defn.NamedTuple(_, tt) =>
tryAdaptPatternArgs(args, tp) match
case Some(args1) =>
args = args1
tt.tupleElementTypes
case _ => None
case _ => None
else tp.widen.normalized.dealias match
case tp @ defn.NamedTuple(_, tt) =>
tryAdaptPatternArgs(validatedTupleElements, tp) match
case Some(args1) =>
args = args1
tt.tupleElementTypes
case _ => None
case _ => None

/** The computed argument types which will be the scutinees of the sub-patterns. */
val argTypes: List[Type] =
Expand Down
16 changes: 16 additions & 0 deletions tests/neg/named-tuples-4.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
-- Error: tests/neg/named-tuples-4.scala:10:35 -------------------------------------------------------------------------
10 | case PersonCaseClass(name = n, age) => () // error
| ^^^
| Illegal combination of named and unnamed tuple elements
-- Error: tests/neg/named-tuples-4.scala:11:31 -------------------------------------------------------------------------
11 | case PersonCaseClass(name, age = a) => () // error
| ^^^^^^^
| Illegal combination of named and unnamed tuple elements
-- Error: tests/neg/named-tuples-4.scala:15:20 -------------------------------------------------------------------------
15 | case (name = n, age) => () // error
| ^^^
| Illegal combination of named and unnamed tuple elements
-- Error: tests/neg/named-tuples-4.scala:16:16 -------------------------------------------------------------------------
16 | case (name, age = a) => () // error
| ^^^^^^^
| Illegal combination of named and unnamed tuple elements
16 changes: 16 additions & 0 deletions tests/neg/named-tuples-4.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import language.experimental.namedTuples
import scala.annotation.experimental

@experimental object Test:

case class PersonCaseClass(name: String, age: Int)

val personCaseClass = PersonCaseClass("Bob", 33)
personCaseClass match
case PersonCaseClass(name = n, age) => () // error
case PersonCaseClass(name, age = a) => () // error

val person = (name = "Bob", age = 33): (name: String, age: Int)
person match
case (name = n, age) => () // error
case (name, age = a) => () // error

0 comments on commit 1bcc03c

Please sign in to comment.