Skip to content

Fix #4984: support name-based unapplySeq #5078

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Sep 20, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Cleanup + Documentation + More neg tests
  • Loading branch information
allanrenucci committed Sep 9, 2018
commit 6a0fc8c60b40e12ac47616f82b7d8b230af16008
34 changes: 23 additions & 11 deletions compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -100,25 +100,37 @@ object Applications {
Nil
}

/** If `getType` is of the form:
* ```
* {
* def lengthCompare(len: Int): Int // or, def length: Int
* def apply(i: Int): T = a(i)
* def drop(n: Int): scala.Seq[T]
* def toSeq: scala.Seq[T]
* }
* ```
* returns `T`, otherwise NoType.
*/
def unapplySeqTypeElemTp(getTp: Type): Type = {
val lengthTp = ExprType(defn.IntType)
val lengthCompareTp = MethodType(List("len".toTermName))(_ => defn.IntType :: Nil, _ => defn.IntType)
def applyTp(elemTp: Type) = MethodType(List("i".toTermName))(_ => defn.IntType :: Nil, _ => elemTp)
def dropTp(elemTp: Type) = MethodType(List("n".toTermName))(_ => defn.IntType :: Nil, _ => defn.SeqType.appliedTo(elemTp))
def lengthTp = ExprType(defn.IntType)
def lengthCompareTp = MethodType(List(defn.IntType), defn.IntType)
def applyTp(elemTp: Type) = MethodType(List(defn.IntType), elemTp)
def dropTp(elemTp: Type) = MethodType(List(defn.IntType), defn.SeqType.appliedTo(elemTp))
def toSeqTp(elemTp: Type) = ExprType(defn.SeqType.appliedTo(elemTp))

// the result type of `def apply(i: Int): T`
val elemTp = getTp.member(nme.apply).suchThat(_.info <:< applyTp(WildcardType)).info.resultType

def test(name: Name, tp: Type) = getTp.member(name).suchThat(getTp.memberInfo(_) <:< tp).exists
def hasMethod(name: Name, tp: Type) =
getTp.member(name).suchThat(getTp.memberInfo(_) <:< tp).exists

val valid =
val isValid =
elemTp.exists &&
(test(nme.lengthCompare, lengthCompareTp) ||
test(nme.length, lengthTp)) &&
test(nme.drop, dropTp(elemTp)) &&
test(nme.toSeq, toSeqTp(elemTp))
(hasMethod(nme.lengthCompare, lengthCompareTp) || hasMethod(nme.length, lengthTp)) &&
hasMethod(nme.drop, dropTp(elemTp)) &&
hasMethod(nme.toSeq, toSeqTp(elemTp))

if (valid) elemTp else NoType
if (isValid) elemTp else NoType
}

if (unapplyName == nme.unapplySeq) {
Expand Down
13 changes: 3 additions & 10 deletions docs/docs/reference/changed/pattern-matching.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,19 +67,12 @@ object FirstChars {

- Extractor defines `def unapplySeq(x: T): U`
- `U` has (parameterless `def` or `val`) members `isEmpty: Boolean` and `get: S`
- `S` conforms to `X` or `Y`, `T2` and `T3` conform to `T1`
- `S` conforms to `X`, `T2` and `T3` conform to `T1`

```Scala
type X = {
def lengthCompare(len: Int): Int
def apply(i: Int): T1 = a(i)
def drop(n: Int): scala.Seq[T2]
def toSeq: scala.Seq[T3]
}

type Y = {
def length: Int
def apply(i: Int): T1 = a(i)
def lengthCompare(len: Int): Int // or, `def length: Int`
def apply(i: Int): T1
def drop(n: Int): scala.Seq[T2]
def toSeq: scala.Seq[T3]
}
Expand Down
30 changes: 21 additions & 9 deletions tests/neg/i4984.scala
Original file line number Diff line number Diff line change
@@ -1,26 +1,38 @@
object Array2 {
def unapplySeq(x: Array[Int]): Data = new Data

final class Data {
class Data {
def isEmpty: Boolean = false
def get: Data = this
def lengthCompare(len: Int): Int = 0
def apply(i: Int): Int = 3
// drop return type, not conforming to apply's
def drop(n: Int): scala.Seq[String] = Seq("hello")
def toSeq: scala.Seq[Int] = Seq(6, 7)
}
}

object Test {
def test1(xs: Array[Int]): Int = xs match {
case Array2(x, y) => x + y // error // error
object Array3 {
def unapplySeq(x: Array[Int]): Data = new Data
class Data {
def isEmpty: Boolean = false
def get: Data = this
def lengthCompare(len: Int): Int = 0
// missing apply
def drop(n: Int): scala.Seq[Int] = ???
def toSeq: scala.Seq[Int] = ???
}
}

def test2(xs: Array[Int]): Seq[Int] = xs match {
case Array2(x, y, xs:_*) => xs // error
object Test {
def test(xs: Array[Int]): Int = xs match {
case Array2(x, y) => 1 // error
case Array2(x, y, xs: _*) => 2 // error
case Array2(xs: _*) => 3 // error
}

def test3(xs: Array[Int]): Seq[Int] = xs match {
case Array2(xs:_*) => xs // error
def test2(xs: Array[Int]): Int = xs match {
case Array3(x, y) => 1 // error
case Array3(x, y, xs: _*) => 2 // error
case Array3(xs: _*) => 3 // error
}
}