Skip to content

Commit 582ebd0

Browse files
committed
Switch handling of regular tuple patterns
When matching a regular tuple pattern against a named tuple selector we adapted the tuple to be named, which could hide errors. We now adapt the selector to be unnamed instead.
1 parent 640c671 commit 582ebd0

File tree

8 files changed

+77
-32
lines changed

8 files changed

+77
-32
lines changed

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

Lines changed: 10 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1461,9 +1461,9 @@ object desugar {
14611461
* (t) ==> t
14621462
* (t1, ..., tN) ==> TupleN(t1, ..., tN)
14631463
*/
1464-
def tuple(tree: Tuple, pt: Type)(using Context): Tree =
1464+
def tuple(tree: Tuple, pt: Type)(using Context): (Tree, Type) =
14651465
checkMismatched(tree.trees)
1466-
val adapted = adaptTupleElems(tree.trees, pt)
1466+
val (adapted, pt1) = adaptTupleElems(tree.trees, pt)
14671467
val elems = adapted.mapConserve(desugarTupleElem)
14681468
val arity = elems.length
14691469
if arity <= Definitions.MaxTupleArity then
@@ -1473,11 +1473,11 @@ object desugar {
14731473
if ctx.mode is Mode.Type then TypeTree(defn.UnitType) else unitLiteral
14741474
else if ctx.mode is Mode.Type then AppliedTypeTree(ref(tupleTypeRef), elems)
14751475
else Apply(ref(tupleTypeRef.classSymbol.companionModule.termRef), elems)
1476-
tree1.withSpan(tree.span)
1476+
(tree1.withSpan(tree.span), pt1)
14771477
else
1478-
cpy.Tuple(tree)(elems)
1478+
(cpy.Tuple(tree)(elems), pt1)
14791479

1480-
def adaptTupleElems(elems: List[Tree], pt: Type)(using Context): List[Tree] =
1480+
def adaptTupleElems(elems: List[Tree], pt: Type)(using Context): (List[Tree], Type) =
14811481

14821482
def reorderedNamedArgs(selElems: List[Type], wildcardSpan: Span): List[untpd.Tree] =
14831483
val nameIdx =
@@ -1497,26 +1497,15 @@ object desugar {
14971497
report.error(em"No element named `$name` is defined", arg.srcPos)
14981498
reordered.toList
14991499

1500-
def convertedToNamedArgs(selElems: List[Type]): List[untpd.Tree] =
1501-
elems.lazyZip(selElems).map:
1502-
case (arg, defn.NamedTupleElem(name, _)) =>
1503-
arg match
1504-
case NamedArg(_, _) => arg // can arise for malformed elements
1505-
case _ => NamedArg(name, arg)
1506-
case (arg, _) =>
1507-
arg
1508-
15091500
pt.tupleElementTypes match
1510-
case Some(selElems @ (firstSelElem :: _)) if ctx.mode.is(Mode.Pattern) =>
1501+
case Some(selElems) if ctx.mode.is(Mode.Pattern) =>
15111502
elems match
15121503
case (first @ NamedArg(_, _)) :: _ =>
1513-
reorderedNamedArgs(selElems, first.span.startPos)
1504+
(reorderedNamedArgs(selElems, first.span.startPos), pt)
15141505
case _ =>
1515-
if firstSelElem.isNamedTupleElem
1516-
then convertedToNamedArgs(selElems)
1517-
else elems
1506+
(elems, pt.dropNamedTupleElems)
15181507
case _ =>
1519-
elems
1508+
(elems, pt)
15201509
end adaptTupleElems
15211510

15221511
private def desugarTupleElem(elem: Tree)(using Context): Tree = elem match
@@ -1527,12 +1516,7 @@ object desugar {
15271516
AppliedTypeTree(ref(defn.Tuple_NamedValueTypeRef),
15281517
SingletonTypeTree(nameLit) :: arg :: Nil)
15291518
else if ctx.mode.is(Mode.Pattern) then
1530-
Apply(
1531-
Block(Nil,
1532-
TypeApply(
1533-
untpd.Select(untpd.ref(defn.Tuple_NamedValueModuleRef), nme.extract),
1534-
SingletonTypeTree(nameLit) :: Nil)),
1535-
arg :: Nil)
1519+
NamedElemPattern(name, arg)
15361520
else
15371521
Apply(
15381522
Select(ref(defn.Tuple_NamedValueModuleRef), nme.apply),

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,31 @@ trait UntypedTreeInfo extends TreeInfo[Untyped] { self: Trees.Instance[Untyped]
481481
if id.span == result.span.startPos => Some(result)
482482
case _ => None
483483
end ImpureByNameTypeTree
484+
485+
/** The desugared version of a named tuple element pattern `name = elem`
486+
* (unapply is currently unused)
487+
*/
488+
object NamedElemPattern:
489+
490+
def apply(name: Name, elem: Tree)(using Context): Tree =
491+
Apply(
492+
Block(Nil,
493+
TypeApply(
494+
untpd.Select(untpd.ref(defn.Tuple_NamedValueModuleRef), nme.extract),
495+
SingletonTypeTree(Literal(Constant(name.toString))) :: Nil)),
496+
elem :: Nil)
497+
498+
def unapply(tree: Tree)(using Context): Option[(TermName, Tree)] = tree match
499+
case Apply(
500+
Block(Nil,
501+
TypeApply(
502+
untpd.Select(TypedSplice(namedValue), nme.extract),
503+
SingletonTypeTree(Literal(Constant(name: String))) :: Nil)),
504+
elem :: Nil) if namedValue.symbol == defn.Tuple_NamedValueModuleRef.symbol =>
505+
Some((name.toTermName, elem))
506+
case _ => None
507+
508+
end NamedElemPattern
484509
}
485510

486511
trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>

compiler/src/dotty/tools/dotc/core/TypeUtils.scala

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,28 @@ class TypeUtils {
8989
case _ => false
9090

9191
/** Is this type a named tuple element `name = value`? */
92-
def isNamedTupleElem(using Context): Boolean = defn.NamedTupleElem.unapply(self).isDefined
92+
def isNamedTupleElem(using Context): Boolean = dropNamedTupleElem ne self
93+
94+
/** Rewrite `name = elem` to `elem` */
95+
def dropNamedTupleElem(using Context) = self match
96+
case defn.NamedTupleElem(_, elem) => elem
97+
case elem => elem
98+
99+
/** Drop all named elements in tuple type */
100+
def dropNamedTupleElems(using Context): Type = self match
101+
case AppliedType(tycon, hd :: tl :: Nil) if tycon.isRef(defn.PairClass) =>
102+
val hd1 = hd.dropNamedTupleElem
103+
val tl1 = tl.dropNamedTupleElems
104+
if (hd1 eq hd) && (tl1 eq tl) then self else AppliedType(tycon, hd1 :: tl1 :: Nil)
105+
case tp @ AppliedType(tycon, args) if defn.isTupleNType(tp) =>
106+
tp.derivedAppliedType(tycon, args.mapConserve(_.dropNamedTupleElem))
107+
case _ =>
108+
if self.termSymbol ne defn.EmptyTupleModule then
109+
val normed = self.widen.normalized.dealias
110+
if normed ne self then
111+
val normed1 = normed.dropNamedTupleElems
112+
if normed1 ne normed then return normed1
113+
self
93114

94115
/** The `*:` equivalent of an instance of a Tuple class */
95116
def toNestedPairs(using Context): Type =

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3071,8 +3071,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
30713071

30723072
/** Translate tuples of all arities */
30733073
def typedTuple(tree: untpd.Tuple, pt: Type)(using Context): Tree =
3074-
val tree1 = desugar.tuple(tree, pt)
3075-
if tree1 ne tree then typed(tree1, pt)
3074+
val (tree1, pt1) = desugar.tuple(tree, pt)
3075+
if (tree1 ne tree) || (pt1 ne pt) then typed(tree1, pt1)
30763076
else
30773077
val arity = tree.trees.length
30783078
val pts = pt.tupleElementTypes match

tests/neg/named-tuples-2.check

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
-- Error: tests/neg/named-tuples-2.scala:5:9 ---------------------------------------------------------------------------
2+
5 | case (name, age) => () // error
3+
| ^
4+
| this case is unreachable since type (name: String, age: Int, married: Boolean) is not a subclass of class Tuple2
5+
-- Error: tests/neg/named-tuples-2.scala:6:9 ---------------------------------------------------------------------------
6+
6 | case (n, a, m, x) => () // error
7+
| ^
8+
| this case is unreachable since type (name: String, age: Int, married: Boolean) is not a subclass of class Tuple4

tests/neg/named-tuples-2.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import language.experimental.namedTuples
2+
def Test =
3+
val person = (name = "Bob", age = 33, married = true)
4+
person match
5+
case (name, age) => () // error
6+
case (n, a, m, x) => () // error

tests/neg/named-tuples.scala

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,3 @@ import language.experimental.namedTuples
3030
person match
3131
case (name = n, age) => () // error
3232
case (name, age = a) => () // error
33-
34-

tests/run/named-tuples.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,5 +90,8 @@ val _: CombinedInfo = bob ++ addr
9090
assert(minors.isEmpty)
9191

9292
bob match
93-
case (age = 33, name = "Bob") => ()
93+
case bob1 @ (age = 33, name = "Bob") =>
94+
val x: Person = bob1 // bob1 still has type Person with the unswapped elements
9495
case _ => assert(false)
96+
97+

0 commit comments

Comments
 (0)