From c01cc270236fd21f7ec3f93d6f8e1ea066a3dce9 Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 1 Jun 2022 18:54:48 +0200 Subject: [PATCH] Better handling of type parameters in constructor pattern In a constructor pattern `C[T](x)` with some type parameter(s), we have two choices. 1. Interpret it as `C[T].unapply(selector)` 2. Interpret it as `C.unapply[T](selector)`. So far we always picked the first choice, which means that usually it will fail with a message such as "`C` does not take type parameters". In this PR, we pick the second choice if it can be typechecked without errors and fall back to the first choice otherwise. --- .../dotty/tools/dotc/typer/Applications.scala | 43 ++++++++++++------- tests/run/unapply-tparam.scala | 36 ++++++++++++++++ 2 files changed, 64 insertions(+), 15 deletions(-) create mode 100644 tests/run/unapply-tparam.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 9e4a1edd6e84..afe8bc2ff390 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1260,9 +1260,14 @@ trait Applications extends Compatibility { */ def trySelectUnapply(qual: untpd.Tree)(fallBack: (Tree, TyperState) => Tree): Tree = { // try first for non-overloaded, then for overloaded ocurrences - def tryWithName(name: TermName)(fallBack: (Tree, TyperState) => Tree)(using Context): Tree = { - def tryWithProto(pt: Type)(using Context) = { - val result = typedExpr(untpd.Select(qual, name), new UnapplyFunProto(pt, this)) + def tryWithName(name: TermName)(fallBack: (Tree, TyperState) => Tree)(using Context): Tree = + + def tryWithProto(qual: untpd.Tree, targs: List[Tree], pt: Type)(using Context) = + val proto = UnapplyFunProto(pt, this) + val unapp = untpd.Select(qual, name) + val result = + if targs.isEmpty then typedExpr(unapp, proto) + else typedExpr(unapp, PolyProto(targs, proto)).appliedToTypeTrees(targs) if !result.symbol.exists || result.symbol.name == name || ctx.reporter.hasErrors @@ -1270,18 +1275,26 @@ trait Applications extends Compatibility { else notAnExtractor(result) // It might be that the result of typedExpr is an `apply` selection or implicit conversion. // Reject in this case. - } - tryEither { - tryWithProto(selType) - } { - (sel, state) => - tryEither { - tryWithProto(WildcardType) - } { - (_, _) => fallBack(sel, state) - } - } - } + + def tryWithTypeArgs(qual: untpd.Tree, targs: List[Tree])(fallBack: (Tree, TyperState) => Tree): Tree = + tryEither { + tryWithProto(qual, targs, selType) + } { + (sel, state) => + tryEither { + tryWithProto(qual, targs, WildcardType) + } { + (_, _) => fallBack(sel, state) + } + } + + qual match + case TypeApply(qual1, targs) => + tryWithTypeArgs(qual1, targs.mapconserve(typedType(_)))((t, ts) => + tryWithTypeArgs(qual, Nil)(fallBack)) + case _ => + tryWithTypeArgs(qual, Nil)(fallBack) + end tryWithName // try first for unapply, then for unapplySeq tryWithName(nme.unapply) { diff --git a/tests/run/unapply-tparam.scala b/tests/run/unapply-tparam.scala new file mode 100644 index 000000000000..71203beaed57 --- /dev/null +++ b/tests/run/unapply-tparam.scala @@ -0,0 +1,36 @@ +class Foo[T] { + def unapply(x: Int): Option[Int] = Some(4) +} + +object Foo { + def unapply[T](x: T): Option[Int] = Some(5) +} + +object Bar { + def unapply[T](x: T): Option[Int] = Some(5) +} + +class Baz[T] { + def unapply(x: Int): Option[Int] = Some(4) +} + +object Baz + + +object Test extends App { + 1 match { + case Foo(x) => assert(x == 5) + } + + 1 match { + case Foo[Int](x) => assert(x == 5) // Type params on object takes precedence + } + + 1 match { + case Bar[Int](x) => assert(x == 5) + } + + 1 match { + case Baz[Int](x) => assert(x == 4) // Otherwise type params are for the class + } +} \ No newline at end of file