Skip to content

Commit 8fed152

Browse files
authored
Merge pull request #8816 from dotty-staging/fix-8730-v2
Fix #8730: disallow structural unapply
2 parents a3a3cf6 + 50a48dc commit 8fed152

File tree

4 files changed

+37
-2
lines changed

4 files changed

+37
-2
lines changed

compiler/src/dotty/tools/dotc/reporting/messages.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1806,7 +1806,7 @@ object messages {
18061806
|""".stripMargin
18071807
}
18081808

1809-
class UnapplyInvalidReturnType(unapplyResult: Type, unapplyName: Symbol#ThisName)(implicit ctx: Context)
1809+
class UnapplyInvalidReturnType(unapplyResult: Type, unapplyName: Name)(implicit ctx: Context)
18101810
extends DeclarationMsg(UnapplyInvalidReturnTypeID) {
18111811
def msg =
18121812
val addendum =

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,10 @@ object Applications {
150150

151151
def unapplyArgs(unapplyResult: Type, unapplyFn: Tree, args: List[untpd.Tree], pos: SourcePosition)(using Context): List[Type] = {
152152

153-
val unapplyName = unapplyFn.symbol.name
153+
val unapplyName = unapplyFn match // tolerate structural `unapply`, which does not have a symbol
154+
case TypeApply(fn: RefTree, _) => fn.name
155+
case fn: RefTree => fn.name
156+
154157
def getTp = extractorMemberType(unapplyResult, nme.get, pos)
155158

156159
def fail = {
@@ -1227,6 +1230,9 @@ trait Applications extends Compatibility {
12271230
case Apply(Apply(unapply, `dummyArg` :: Nil), args2) => assert(args2.nonEmpty); args2
12281231
case Apply(unapply, `dummyArg` :: Nil) => Nil
12291232
case Inlined(u, _, _) => unapplyImplicits(u)
1233+
case DynamicUnapply(_) =>
1234+
ctx.error("Structural unapply is not supported", unapplyFn.sourcePos)
1235+
Nil
12301236
case _ => Nil.assertingErrorsReported
12311237
}
12321238

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,17 @@ object Dynamic {
2222
name == nme.applyDynamic || name == nme.selectDynamic || name == nme.updateDynamic || name == nme.applyDynamicNamed
2323
}
2424

25+
object DynamicUnapply {
26+
def unapply(tree: tpd.Tree): Option[List[tpd.Tree]] = tree match
27+
case TypeApply(Select(qual, name), _) if name == nme.asInstanceOfPM =>
28+
unapply(qual)
29+
case Apply(Apply(Select(selectable, fname), Literal(Constant(name)) :: ctag :: Nil), _ :: implicits)
30+
if fname == nme.applyDynamic && (name == "unapply" || name == "unapplySeq") =>
31+
Some(selectable :: ctag :: implicits)
32+
case _ =>
33+
None
34+
}
35+
2536
/** Handles programmable member selections of `Dynamic` instances and values
2637
* with structural types. Two functionalities:
2738
*

tests/neg/i8730.scala

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import reflect.Selectable.reflectiveSelectable
2+
3+
class Nat(val x: Int) {
4+
def get: Int = x
5+
def isEmpty = x < 0
6+
}
7+
8+
val SomeExtractorBuilder: { def unapply(x: Int): Nat } = new {
9+
def unapply(x: Int): Nat = new Nat(x)
10+
}
11+
12+
13+
@main
14+
def Test = 5 match {
15+
case SomeExtractorBuilder(n) => println(s"$n is a natural number") // error
16+
case _ => ()
17+
}
18+

0 commit comments

Comments
 (0)