From 854d7cec74139217381a501c278b9792fc474136 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Mon, 16 Jun 2025 12:29:09 +0200 Subject: [PATCH] Refine bind tuple pattern typing for named tuples --- .../src/dotty/tools/dotc/typer/Typer.scala | 17 +++++++++++++++-- tests/run/bind-tuple-pattern.scala | 19 +++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 tests/run/bind-tuple-pattern.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 0af1685d0857..3b6e125fbeb4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2774,6 +2774,20 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer if !isFullyDefined(pt, ForceDegree.all) then return errorTree(tree, em"expected type of $tree is not fully defined") val body1 = typed(tree.body, pt) + + // When we pattern match a named tuple, both the named tuple pattern and the + // regular tuple pattern are desugared to a regular tuple unapply. + // If the pattern (body) is a named tuple pattern, we give the binding + // a named tuple type using pt; otherwise we give it the regular tuple type. + // For example, in `case x @ (a = 1, b = 2)`, the type of `x` will be `(a: Int, b: Int)`; + // in `case x @ (a, b)`, the type of `x` will be `(Int, Int)`. + def isNamedTuplePattern = + ctx.mode.is(Mode.Pattern) + && pt.dealias.isNamedTupleType + && tree.body.match + case untpd.Tuple((_: NamedArg) :: _) => true + case _ => false + body1 match { case UnApply(fn, Nil, arg :: Nil) if fn.symbol.exists && (fn.symbol.owner.derivesFrom(defn.TypeTestClass) || fn.symbol.owner == defn.ClassTagClass) && !body1.tpe.isError => @@ -2799,8 +2813,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer body1.isInstanceOf[RefTree] && !isWildcardArg(body1) || body1.isInstanceOf[Literal] val symTp = - if isStableIdentifierOrLiteral || pt.dealias.isNamedTupleType then pt - // need to combine tuple element types with expected named type + if isStableIdentifierOrLiteral || isNamedTuplePattern then pt else if isWildcardStarArg(body1) || pt == defn.ImplicitScrutineeTypeRef || body1.tpe <:< pt // There is some strange interaction with gadt matching. diff --git a/tests/run/bind-tuple-pattern.scala b/tests/run/bind-tuple-pattern.scala new file mode 100644 index 000000000000..e4d425fabe2c --- /dev/null +++ b/tests/run/bind-tuple-pattern.scala @@ -0,0 +1,19 @@ +import annotation.experimental + +def getNamedTuple: (x: Int, y: String) = (x = 42, y = "Hello") + +@main def Test = + getNamedTuple match + case (x, y) => assert(x == 42 && y == "Hello") + + getNamedTuple match + case t @ (x = a, y = b) => + // t binds to a named tuple pattern + // t: (x: Int, y: String) + assert(a == t.x && b == t.y) + + getNamedTuple match + case t @ (a, b) => + // t binds to a regular tuple pattern + // t: (Int, String) + assert(t._1 == a && t._2 == b) \ No newline at end of file