diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index e10b9bc7165b..e51aa32f3622 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -1806,7 +1806,7 @@ object messages { |""".stripMargin } - class UnapplyInvalidReturnType(unapplyResult: Type, unapplyName: Symbol#ThisName)(implicit ctx: Context) + class UnapplyInvalidReturnType(unapplyResult: Type, unapplyName: Name)(implicit ctx: Context) extends DeclarationMsg(UnapplyInvalidReturnTypeID) { def msg = val addendum = diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 063d7f1e8014..e98c3440281e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -150,7 +150,10 @@ object Applications { def unapplyArgs(unapplyResult: Type, unapplyFn: Tree, args: List[untpd.Tree], pos: SourcePosition)(using Context): List[Type] = { - val unapplyName = unapplyFn.symbol.name + val unapplyName = unapplyFn match // tolerate structural `unapply`, which does not have a symbol + case TypeApply(fn: RefTree, _) => fn.name + case fn: RefTree => fn.name + def getTp = extractorMemberType(unapplyResult, nme.get, pos) def fail = { @@ -1227,6 +1230,9 @@ trait Applications extends Compatibility { case Apply(Apply(unapply, `dummyArg` :: Nil), args2) => assert(args2.nonEmpty); args2 case Apply(unapply, `dummyArg` :: Nil) => Nil case Inlined(u, _, _) => unapplyImplicits(u) + case DynamicUnapply(_) => + ctx.error("Structural unapply is not supported", unapplyFn.sourcePos) + Nil case _ => Nil.assertingErrorsReported } diff --git a/compiler/src/dotty/tools/dotc/typer/Dynamic.scala b/compiler/src/dotty/tools/dotc/typer/Dynamic.scala index 5cd5f6c4b410..50fc5302dafd 100644 --- a/compiler/src/dotty/tools/dotc/typer/Dynamic.scala +++ b/compiler/src/dotty/tools/dotc/typer/Dynamic.scala @@ -22,6 +22,17 @@ object Dynamic { name == nme.applyDynamic || name == nme.selectDynamic || name == nme.updateDynamic || name == nme.applyDynamicNamed } +object DynamicUnapply { + def unapply(tree: tpd.Tree): Option[List[tpd.Tree]] = tree match + case TypeApply(Select(qual, name), _) if name == nme.asInstanceOfPM => + unapply(qual) + case Apply(Apply(Select(selectable, fname), Literal(Constant(name)) :: ctag :: Nil), _ :: implicits) + if fname == nme.applyDynamic && (name == "unapply" || name == "unapplySeq") => + Some(selectable :: ctag :: implicits) + case _ => + None +} + /** Handles programmable member selections of `Dynamic` instances and values * with structural types. Two functionalities: * diff --git a/tests/neg/i8730.scala b/tests/neg/i8730.scala new file mode 100644 index 000000000000..f68bcdc9b1fa --- /dev/null +++ b/tests/neg/i8730.scala @@ -0,0 +1,18 @@ +import reflect.Selectable.reflectiveSelectable + +class Nat(val x: Int) { + def get: Int = x + def isEmpty = x < 0 +} + +val SomeExtractorBuilder: { def unapply(x: Int): Nat } = new { + def unapply(x: Int): Nat = new Nat(x) +} + + +@main +def Test = 5 match { + case SomeExtractorBuilder(n) => println(s"$n is a natural number") // error + case _ => () +} +