Skip to content

Commit

Permalink
Fail more eagerly when trying to adapt named unapply patterns (#22315)
Browse files Browse the repository at this point in the history
closes #22192
  • Loading branch information
odersky authored Jan 9, 2025
2 parents 045434c + 1fbaafc commit 0ecc057
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 4 deletions.
17 changes: 13 additions & 4 deletions compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ object Applications {
if (isValid) elemTp else NoType
}

def namedTupleOrProductTypes(tp: Type)(using Context): List[Type] =
if tp.isNamedTupleType then tp.namedTupleElementTypes.map(_(1))
else productSelectorTypes(tp, NoSourcePosition)

def productSelectorTypes(tp: Type, errorPos: SrcPos)(using Context): List[Type] = {
val sels = for (n <- Iterator.from(0)) yield extractorMemberType(tp, nme.selectorName(n), errorPos)
sels.takeWhile(_.exists).toList
Expand Down Expand Up @@ -177,9 +181,14 @@ object Applications {
else fallback

private def tryAdaptPatternArgs(elems: List[untpd.Tree], pt: Type)(using Context): Option[List[untpd.Tree]] =
tryEither[Option[List[untpd.Tree]]]
(Some(desugar.adaptPatternArgs(elems, pt)))
((_, _) => None)
namedTupleOrProductTypes(pt) match
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)))
((_, _) => None)
case pts =>
Some(desugar.adaptPatternArgs(elems, pt))

private def getUnapplySelectors(tp: Type)(using Context): List[Type] =
// We treat patterns as product elements if
Expand All @@ -199,7 +208,7 @@ object Applications {
else tp :: Nil

private def productUnapplySelectors(tp: Type)(using Context): Option[List[Type]] =
if defn.isProductSubType(tp) then
if defn.isProductSubType(tp) && args.lengthCompare(productArity(tp)) <= 0 then
tryAdaptPatternArgs(args, tp) match
case Some(args1) if isProductMatch(tp, args1.length, pos) =>
args = args1
Expand Down
30 changes: 30 additions & 0 deletions tests/neg/i22192.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
-- Error: tests/neg/i22192.scala:6:12 ----------------------------------------------------------------------------------
6 | case City(iam = n, confused = p) => // error // error
| ^^^^^^^
| No element named `iam` is defined in selector type City
-- Error: tests/neg/i22192.scala:6:21 ----------------------------------------------------------------------------------
6 | case City(iam = n, confused = p) => // error // error
| ^^^^^^^^^^^^
| No element named `confused` is defined in selector type City
-- [E006] Not Found Error: tests/neg/i22192.scala:7:7 ------------------------------------------------------------------
7 | s"$n has a population of $p" // error // error
| ^
| Not found: n
|
| longer explanation available when compiling with `-explain`
-- [E006] Not Found Error: tests/neg/i22192.scala:7:30 -----------------------------------------------------------------
7 | s"$n has a population of $p" // error // error
| ^
| Not found: p
|
| longer explanation available when compiling with `-explain`
-- Error: tests/neg/i22192.scala:10:12 ---------------------------------------------------------------------------------
10 | case Some(iam = n) => // error
| ^^^^^^^
| No element named `iam` is defined in selector type City
-- [E006] Not Found Error: tests/neg/i22192.scala:11:4 -----------------------------------------------------------------
11 | n // error
| ^
| Not found: n
|
| longer explanation available when compiling with `-explain`
11 changes: 11 additions & 0 deletions tests/neg/i22192.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import scala.language.experimental.namedTuples

case class City(name: String, population: Int)

def getCityInfo(city: City) = city match
case City(iam = n, confused = p) => // error // error
s"$n has a population of $p" // error // error

def getCityInfo1(city: Option[City]) = city match
case Some(iam = n) => // error
n // error
20 changes: 20 additions & 0 deletions tests/neg/i22192a.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
-- Error: tests/neg/i22192a.scala:6:12 ---------------------------------------------------------------------------------
6 | case Some(iam = n) => // error
| ^^^^^^^
| No element named `iam` is defined in selector type (name : String)
-- [E006] Not Found Error: tests/neg/i22192a.scala:7:4 -----------------------------------------------------------------
7 | n // error
| ^
| Not found: n
|
| longer explanation available when compiling with `-explain`
-- Error: tests/neg/i22192a.scala:11:12 --------------------------------------------------------------------------------
11 | case Some(iam = n) => // error
| ^^^^^^^
| No element named `iam` is defined in selector type (name : String, population : Int)
-- [E006] Not Found Error: tests/neg/i22192a.scala:12:4 ----------------------------------------------------------------
12 | n // error
| ^
| Not found: n
|
| longer explanation available when compiling with `-explain`
13 changes: 13 additions & 0 deletions tests/neg/i22192a.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import scala.language.experimental.namedTuples

type City = (name: String)

def getCityInfo(city: Option[City]) = city match
case Some(iam = n) => // error
n // error
case _ =>

def getCiryInfo1(city: Option[(name: String, population: Int)]) = city match
case Some(iam = n) => // error
n // error
case _ =>
15 changes: 15 additions & 0 deletions tests/pos/i22192.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import scala.language.experimental.namedTuples

case class City(name: String, population: Int)

def getCityInfo(city: City) = city match
case City(population = p, name = n) =>
s"$n has a population of $p"

def getCityInfo1(city: Option[(name: String)]) = city match
case Some(name = n) => n
case _ =>

def getCityInfo2(city: Option[(name: String, population: Int)]) = city match
case Some(name = n) => n
case _ =>

0 comments on commit 0ecc057

Please sign in to comment.