From d5b5defe8b41bbf249f853dd90dccfac35b36592 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Tue, 14 Jan 2025 11:57:26 +0100 Subject: [PATCH] Fix #21841: Check more that an `unapplySeq` on a `NonEmptyTuple` is valid. --- .../dotty/tools/dotc/typer/Applications.scala | 36 ++++++++++++++----- tests/neg/i21841.check | 6 ++++ tests/neg/i21841.scala | 22 ++++++++++++ 3 files changed, 56 insertions(+), 8 deletions(-) create mode 100644 tests/neg/i21841.check create mode 100644 tests/neg/i21841.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index c2864093ff70..7b3134fb2003 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -68,6 +68,21 @@ object Applications { unapplySeqTypeElemTp(productSelectorTypes(tp, errorPos).last).exists } + /** Does `tp` fit the "product-seq match" conditions for a `NonEmptyTuple` as + * an unapply result type for a pattern with `numArgs` subpatterns? + * This is the case if (1) `tp` derives from `NonEmptyTuple`. + * (2) `tp.tupleElementTypes` exists. + * (3) `tp.tupleElementTypes.last` conforms to Seq match + */ + def isNonEmptyTupleSeqMatch(tp: Type, numArgs: Int, errorPos: SrcPos = NoSourcePosition)(using Context): Boolean = { + tp.derivesFrom(defn.NonEmptyTupleClass) + && tp.tupleElementTypes.exists { elemTypes => + val arity = elemTypes.size + arity > 0 && arity <= numArgs + 1 && + unapplySeqTypeElemTp(elemTypes.last).exists + } + } + /** Does `tp` fit the "get match" conditions as an unapply result type? * This is the case of `tp` has a `get` member as well as a * parameterless `isEmpty` member of result type `Boolean`. @@ -140,12 +155,17 @@ object Applications { sels.takeWhile(_.exists).toList } - def productSeqSelectors(tp: Type, argsNum: Int, pos: SrcPos)(using Context): List[Type] = { - val selTps = productSelectorTypes(tp, pos) - val arity = selTps.length - val elemTp = unapplySeqTypeElemTp(selTps.last) - (0 until argsNum).map(i => if (i < arity - 1) selTps(i) else elemTp).toList - } + def productSeqSelectors(tp: Type, argsNum: Int, pos: SrcPos)(using Context): List[Type] = + seqSelectors(productSelectorTypes(tp, pos), argsNum) + + def nonEmptyTupleSeqSelectors(tp: Type, argsNum: Int, pos: SrcPos)(using Context): List[Type] = + seqSelectors(tp.tupleElementTypes.get, argsNum) + + private def seqSelectors(selectorTypes: List[Type], argsNum: Int)(using Context): List[Type] = + val arity = selectorTypes.length + val elemTp = unapplySeqTypeElemTp(selectorTypes.last) + (0 until argsNum).map(i => if (i < arity - 1) selectorTypes(i) else elemTp).toList + end seqSelectors /** A utility class that matches results of unapplys with patterns. Two queriable members: * val argTypes: List[Type] @@ -176,8 +196,8 @@ object Applications { args.map(Function.const(elemTp)) else if isProductSeqMatch(tp, args.length, pos) then productSeqSelectors(tp, args.length, pos) - else if tp.derivesFrom(defn.NonEmptyTupleClass) then - tp.tupleElementTypes.getOrElse(Nil) + else if isNonEmptyTupleSeqMatch(tp, args.length, pos) then + nonEmptyTupleSeqSelectors(tp, args.length, pos) else fallback private def tryAdaptPatternArgs(elems: List[untpd.Tree], pt: Type)(using Context): Option[List[untpd.Tree]] = diff --git a/tests/neg/i21841.check b/tests/neg/i21841.check new file mode 100644 index 000000000000..e119d650b360 --- /dev/null +++ b/tests/neg/i21841.check @@ -0,0 +1,6 @@ +-- [E108] Declaration Error: tests/neg/i21841.scala:20:13 -------------------------------------------------------------- +20 | case v[T](l, r) => () // error + | ^^^^^^^^^^ + | Option[(Test.Expr[Test.T], Test.Expr[Test.T])] is not a valid result type of an unapplySeq method of an extractor. + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i21841.scala b/tests/neg/i21841.scala new file mode 100644 index 000000000000..8a280abc2cf8 --- /dev/null +++ b/tests/neg/i21841.scala @@ -0,0 +1,22 @@ +object Test { + + sealed trait T + sealed trait Arrow[A, B] + + type ArgsTo[S1, Target] <: NonEmptyTuple = S1 match { + case Arrow[a, Target] => Tuple1[Expr[a]] + case Arrow[a, b] => Expr[a] *: ArgsTo[b, Target] + } + + sealed trait Expr[S] : + def unapplySeq[Target](e: Expr[Target]): Option[ArgsTo[S, Target]] = ??? + + case class Variable[S](id: String) extends Expr[S] + + val v = Variable[Arrow[T, Arrow[T, T]]]("v") + val e : Expr[T] = ??? + + e match + case v[T](l, r) => () // error + case _ => () +}