Skip to content

Commit 4025987

Browse files
committed
Fix #3248: Handle repeated arguments in results of unapply selectors
Normal unapply selectors can return results that have a repeated argument as last element. This needs to be handled. This also subsumes the previous way to express such arguments with `unapplySeq`. In the long run, we should be able to do without `unapplySeq` altogether. This is an intermediate step to get there.
1 parent dc1bf9f commit 4025987

File tree

6 files changed

+74
-20
lines changed

6 files changed

+74
-20
lines changed

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

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1363,6 +1363,15 @@ object Types {
13631363
*/
13641364
def signature(implicit ctx: Context): Signature = Signature.NotAMethod
13651365

1366+
def annotatedToRepeated(implicit ctx: Context): Type = this match {
1367+
case tp @ ExprType(tp1) => tp.derivedExprType(tp1.annotatedToRepeated)
1368+
case AnnotatedType(tp, annot) if annot matches defn.RepeatedAnnot =>
1369+
val typeSym = tp.typeSymbol.asClass
1370+
assert(typeSym == defn.SeqClass || typeSym == defn.ArrayClass)
1371+
tp.translateParameterized(typeSym, defn.RepeatedParamClass)
1372+
case _ => this
1373+
}
1374+
13661375
/** Convert to text */
13671376
def toText(printer: Printer): Text = printer.toText(this)
13681377

@@ -2860,21 +2869,12 @@ object Types {
28602869
* - add @inlineParam to inline call-by-value parameters
28612870
*/
28622871
def fromSymbols(params: List[Symbol], resultType: Type)(implicit ctx: Context) = {
2863-
def translateRepeated(tp: Type): Type = tp match {
2864-
case tp @ ExprType(tp1) => tp.derivedExprType(translateRepeated(tp1))
2865-
case AnnotatedType(tp, annot) if annot matches defn.RepeatedAnnot =>
2866-
val typeSym = tp.typeSymbol.asClass
2867-
assert(typeSym == defn.SeqClass || typeSym == defn.ArrayClass)
2868-
tp.translateParameterized(typeSym, defn.RepeatedParamClass)
2869-
case tp =>
2870-
tp
2871-
}
28722872
def translateInline(tp: Type): Type = tp match {
28732873
case _: ExprType => tp
28742874
case _ => AnnotatedType(tp, Annotation(defn.InlineParamAnnot))
28752875
}
28762876
def paramInfo(param: Symbol) = {
2877-
val paramType = translateRepeated(param.info)
2877+
val paramType = param.info.annotatedToRepeated
28782878
if (param.is(Inline)) translateInline(paramType) else paramType
28792879
}
28802880

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

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ object Applications {
4747
val ref = extractorMember(tp, name)
4848
if (ref.isOverloaded)
4949
errorType(i"Overloaded reference to $ref is not allowed in extractor", errorPos)
50-
ref.info.widenExpr.dealias
50+
ref.info.widenExpr.annotatedToRepeated.dealias
5151
}
5252

5353
/** Does `tp` fit the "product match" conditions as an unapply result type
@@ -93,7 +93,10 @@ object Applications {
9393
def getTp = extractorMemberType(unapplyResult, nme.get, pos)
9494

9595
def fail = {
96-
ctx.error(i"$unapplyResult is not a valid result type of an $unapplyName method of an extractor", pos)
96+
val addendum =
97+
if (ctx.scala2Mode && unapplyName == nme.unapplySeq)
98+
"\n You might want to try to rewrite the extractor to use `unapply` instead."
99+
ctx.error(em"$unapplyResult is not a valid result type of an $unapplyName method of an extractor$addendum", pos)
97100
Nil
98101
}
99102

@@ -959,13 +962,19 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
959962

960963
var argTypes = unapplyArgs(unapplyApp.tpe, unapplyFn, args, tree.pos)
961964
for (argType <- argTypes) assert(!argType.isInstanceOf[TypeBounds], unapplyApp.tpe.show)
962-
val bunchedArgs = argTypes match {
963-
case argType :: Nil =>
964-
if (argType.isRepeatedParam) untpd.SeqLiteral(args, untpd.TypeTree()) :: Nil
965-
else if (args.lengthCompare(1) > 0 && ctx.canAutoTuple) untpd.Tuple(args) :: Nil
966-
else args
967-
case _ => args
968-
}
965+
val bunchedArgs =
966+
if (argTypes.nonEmpty && argTypes.last.isRepeatedParam)
967+
args.lastOption match {
968+
case Some(arg @ Typed(argSeq, _)) if untpd.isWildcardStarArg(arg) =>
969+
args.init :+ argSeq
970+
case _ =>
971+
val (regularArgs, varArgs) = args.splitAt(argTypes.length - 1)
972+
regularArgs :+ untpd.SeqLiteral(varArgs, untpd.TypeTree())
973+
}
974+
else if (argTypes.lengthCompare(1) == 0 && args.lengthCompare(1) > 0 && ctx.canAutoTuple)
975+
untpd.Tuple(args) :: Nil
976+
else
977+
args
969978
if (argTypes.length != bunchedArgs.length) {
970979
ctx.error(UnapplyInvalidNumberOfArguments(qual, argTypes), tree.pos)
971980
argTypes = argTypes.take(args.length) ++

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1275,7 +1275,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
12751275
case _ =>
12761276
if (tree.name == nme.WILDCARD) body1
12771277
else {
1278-
val sym = newPatternBoundSym(tree.name, body1.tpe, tree.pos)
1278+
val sym = newPatternBoundSym(tree.name, body1.tpe.underlyingIfRepeated(isJava = false), tree.pos)
12791279
if (ctx.mode.is(Mode.InPatternAlternative))
12801280
ctx.error(i"Illegal variable ${sym.name} in pattern alternative", tree.pos)
12811281
assignType(cpy.Bind(tree)(tree.name, body1), sym)

tests/neg/i3248.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
class Test {
2+
class Foo(val name: String, val children: Int *)
3+
object Foo {
4+
def unapplySeq(f: Foo) = Some((f.name, f.children))
5+
}
6+
7+
def foo(f: Foo) = f match {
8+
case Foo(name, ns: _*) => 1 // error: not a valid unapply result type
9+
}
10+
}

tests/pos/i3248.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
object Test {
2+
class Foo(val name: String, val children: Int *)
3+
object Foo {
4+
def unapply(f: Foo) = Some((f.name, f.children))
5+
}
6+
7+
def foo(f: Foo) = f match {
8+
case Foo(name, cs : _*) => name :: cs.reverse.toList.map(_.toString)
9+
}
10+
def main(args: Array[String]) = {
11+
println(foo(new Foo("hi", 1, 2, 3)).mkString(" "))
12+
}
13+
}

tests/run/i3248.scala

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
object Test extends App {
2+
class Foo(val name: String, val children: Int *)
3+
object Foo {
4+
def unapply(f: Foo) = Some((f.name, f.children))
5+
}
6+
7+
def foo(f: Foo) = (f: Any) match {
8+
case Foo(name, ns: _*) => ns.length
9+
case List(ns: _*) => ns.length
10+
}
11+
12+
case class Bar(val children: Int*)
13+
14+
def bar(f: Any) = f match {
15+
case Bar(1, 2, 3) => 0
16+
case Bar(ns: _*) => ns.length
17+
}
18+
19+
assert(bar(new Bar(1, 2, 3)) == 0)
20+
assert(bar(new Bar(3, 2, 1)) == 3)
21+
assert(foo(new Foo("name", 1, 2, 3)) == 3)
22+
}

0 commit comments

Comments
 (0)