From 2a5e33ffab6503f6f0bd4cd27d9553d48891196f Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Tue, 4 Sep 2018 14:30:16 +0200 Subject: [PATCH 01/10] Fix #4984: support name-based unapplySeq --- .../dotty/tools/dotc/core/Definitions.scala | 2 ++ .../tools/dotc/transform/PatternMatcher.scala | 9 ++++-- .../dotty/tools/dotc/typer/Applications.scala | 21 ++++++++++-- tests/pos/i4984.scala | 26 +++++++++++++++ tests/run/i4984b.scala | 32 +++++++++++++++++++ 5 files changed, 86 insertions(+), 4 deletions(-) create mode 100644 tests/pos/i4984.scala create mode 100644 tests/run/i4984b.scala diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 0483f699fc3d..50b058858bb3 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -422,6 +422,8 @@ class Definitions { def Seq_drop(implicit ctx: Context) = Seq_dropR.symbol lazy val Seq_lengthCompareR = SeqClass.requiredMethodRef(nme.lengthCompare) def Seq_lengthCompare(implicit ctx: Context) = Seq_lengthCompareR.symbol + lazy val Seq_toSeqR = SeqClass.requiredMethodRef(nme.toSeq) + def Seq_toSeq(implicit ctx: Context) = Seq_toSeqR.symbol lazy val ArrayType: TypeRef = ctx.requiredClassRef("scala.Array") def ArrayClass(implicit ctx: Context) = ArrayType.symbol.asClass diff --git a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala index 803d234e89dc..cf7e6e7ec86e 100644 --- a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -265,8 +265,13 @@ object PatternMatcher { def unapplySeqPlan(getResult: Symbol, args: List[Tree]): Plan = args.lastOption match { case Some(VarArgPattern(arg)) => val matchRemaining = - if (args.length == 1) - patternPlan(getResult, arg, onSuccess) + if (args.length == 1) { + val toSeq = ref(getResult) + .select(defn.Seq_toSeq.matchingMember(getResult.info)) + letAbstract(toSeq) { toSeqResult => + patternPlan(toSeqResult, arg, onSuccess) + } + } else { val dropped = ref(getResult) .select(defn.Seq_drop.matchingMember(getResult.info)) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index bce6fb03d7eb..8795082ad35d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -100,10 +100,27 @@ object Applications { Nil } + def vaidUnapplySeqType(getTp: Type): Boolean = { + def superType(elemTp: Type) = { + val tps = List( + MethodType(List("len".toTermName))(_ => defn.IntType :: Nil, _ => defn.IntType), + MethodType(List("i".toTermName))(_ => defn.IntType :: Nil, _ => elemTp), + MethodType(List("n".toTermName))(_ => defn.IntType :: Nil, _ => defn.SeqType.appliedTo(elemTp)), + ExprType(defn.SeqType.appliedTo(elemTp)), + ) + val names = List(nme.lengthCompare, nme.apply, nme.drop, nme.toSeq) + RefinedType.make(defn.AnyType, names, tps) + } + getTp <:< superType(WildcardType) && { + val seqArg = getTp.member(nme.toSeq).info.elemType.hiBound + getTp <:< superType(seqArg) + } + } + if (unapplyName == nme.unapplySeq) { if (unapplyResult derivesFrom defn.SeqClass) seqSelector :: Nil - else if (isGetMatch(unapplyResult, pos) && getTp.derivesFrom(defn.SeqClass)) { - val seqArg = getTp.elemType.hiBound + else if (isGetMatch(unapplyResult, pos) && vaidUnapplySeqType(getTp)) { + val seqArg = getTp.member(nme.apply).info.finalResultType if (seqArg.exists) args.map(Function.const(seqArg)) else fail } diff --git a/tests/pos/i4984.scala b/tests/pos/i4984.scala new file mode 100644 index 000000000000..f81f068113ed --- /dev/null +++ b/tests/pos/i4984.scala @@ -0,0 +1,26 @@ +object Array2 { + def unapplySeq[T](x: Array[T]): UnapplySeqWrapper[T] = new UnapplySeqWrapper(x) + + final class UnapplySeqWrapper[T](private val a: Array[T]) extends AnyVal { + def isEmpty: Boolean = false + def get: UnapplySeqWrapper[T] = this + def lengthCompare(len: Int): Int = a.lengthCompare(len) + def apply(i: Int): T = a(i) + def drop(n: Int): scala.Seq[T] = ??? + def toSeq: scala.Seq[T] = a.toSeq // clones the array + } +} + +class Test { + def test1(xs: Array[Int]): Int = xs match { + case Array2(x, y) => x + y + } + + def test2(xs: Array[Int]): Seq[Int] = xs match { + case Array2(x, y, xs:_*) => xs + } + + def test3(xs: Array[Int]): Seq[Int] = xs match { + case Array2(xs:_*) => xs + } +} diff --git a/tests/run/i4984b.scala b/tests/run/i4984b.scala new file mode 100644 index 000000000000..7f9beb62128c --- /dev/null +++ b/tests/run/i4984b.scala @@ -0,0 +1,32 @@ +object Array2 { + def unapplySeq(x: Array[Int]): Data = new Data + + final class Data { + def isEmpty: Boolean = false + def get: Data = this + def lengthCompare(len: Int): Int = 0 + def apply(i: Int): Int = 3 + def drop(n: Int): scala.Seq[Int] = Seq(2, 5) + def toSeq: scala.Seq[Int] = Seq(6, 7) + } +} + +object Test { + def test1(xs: Array[Int]): Int = xs match { + case Array2(x, y) => x + y + } + + def test2(xs: Array[Int]): Seq[Int] = xs match { + case Array2(x, y, xs:_*) => xs + } + + def test3(xs: Array[Int]): Seq[Int] = xs match { + case Array2(xs:_*) => xs + } + + def main(args: Array[String]): Unit = { + test1(Array(3, 5)) + test2(Array(3, 5)) + test3(Array(3, 5)) + } +} From 48b7dbb6ab1d778a6548c5a5b641688bbccb5c76 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Tue, 4 Sep 2018 14:42:53 +0200 Subject: [PATCH 02/10] use toSeq type as element type and check type --- compiler/src/dotty/tools/dotc/typer/Applications.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 8795082ad35d..77a38cd78906 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -100,7 +100,7 @@ object Applications { Nil } - def vaidUnapplySeqType(getTp: Type): Boolean = { + def validUnapplySeqType(getTp: Type): Boolean = { def superType(elemTp: Type) = { val tps = List( MethodType(List("len".toTermName))(_ => defn.IntType :: Nil, _ => defn.IntType), @@ -112,15 +112,15 @@ object Applications { RefinedType.make(defn.AnyType, names, tps) } getTp <:< superType(WildcardType) && { - val seqArg = getTp.member(nme.toSeq).info.elemType.hiBound + val seqArg = extractorMemberType(getTp, nme.toSeq).elemType.hiBound getTp <:< superType(seqArg) } } if (unapplyName == nme.unapplySeq) { if (unapplyResult derivesFrom defn.SeqClass) seqSelector :: Nil - else if (isGetMatch(unapplyResult, pos) && vaidUnapplySeqType(getTp)) { - val seqArg = getTp.member(nme.apply).info.finalResultType + else if (isGetMatch(unapplyResult, pos) && validUnapplySeqType(getTp)) { + val seqArg = extractorMemberType(getTp, nme.toSeq).elemType.hiBound if (seqArg.exists) args.map(Function.const(seqArg)) else fail } From 6999ca8dc350d9f0035d161aeb8017f99d00431b Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Tue, 4 Sep 2018 15:32:48 +0200 Subject: [PATCH 03/10] add neg test --- tests/neg/i4984.scala | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 tests/neg/i4984.scala diff --git a/tests/neg/i4984.scala b/tests/neg/i4984.scala new file mode 100644 index 000000000000..19a236ce462d --- /dev/null +++ b/tests/neg/i4984.scala @@ -0,0 +1,26 @@ +object Array2 { + def unapplySeq(x: Array[Int]): Data = new Data + + final class Data { + def isEmpty: Boolean = false + def get: Data = this + def lengthCompare(len: Int): Int = 0 + def apply(i: Int): Int = 3 + 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 + } + + def test2(xs: Array[Int]): Seq[Int] = xs match { + case Array2(x, y, xs:_*) => xs // error + } + + def test3(xs: Array[Int]): Seq[Int] = xs match { + case Array2(xs:_*) => xs // error + } +} From 4c12ed3885d06c1bd86e8f8b6458f83e28da8ae7 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Thu, 6 Sep 2018 00:12:53 +0200 Subject: [PATCH 04/10] address review: try length if lengthCompare unavailable --- .../dotty/tools/dotc/core/Definitions.scala | 2 + .../tools/dotc/transform/PatternMatcher.scala | 17 +++++--- .../dotty/tools/dotc/typer/Applications.scala | 41 +++++++++++-------- tests/run/i4984c.scala | 32 +++++++++++++++ 4 files changed, 69 insertions(+), 23 deletions(-) create mode 100644 tests/run/i4984c.scala diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 50b058858bb3..328cedbad81f 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -422,6 +422,8 @@ class Definitions { def Seq_drop(implicit ctx: Context) = Seq_dropR.symbol lazy val Seq_lengthCompareR = SeqClass.requiredMethodRef(nme.lengthCompare) def Seq_lengthCompare(implicit ctx: Context) = Seq_lengthCompareR.symbol + lazy val Seq_lengthR = SeqClass.requiredMethodRef(nme.length) + def Seq_length(implicit ctx: Context) = Seq_lengthR.symbol lazy val Seq_toSeqR = SeqClass.requiredMethodRef(nme.toSeq) def Seq_toSeq(implicit ctx: Context) = Seq_toSeqR.symbol diff --git a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala index cf7e6e7ec86e..2e4c8ce1cff5 100644 --- a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -643,11 +643,18 @@ object PatternMatcher { case EqualTest(tree) => tree.equal(scrutinee) case LengthTest(len, exact) => - scrutinee - .select(defn.Seq_lengthCompare.matchingMember(scrutinee.tpe)) - .appliedTo(Literal(Constant(len))) - .select(if (exact) defn.Int_== else defn.Int_>=) - .appliedTo(Literal(Constant(0))) + val lengthCompareSym = defn.Seq_lengthCompare.matchingMember(scrutinee.tpe) + if (lengthCompareSym.exists) + scrutinee + .select(defn.Seq_lengthCompare.matchingMember(scrutinee.tpe)) + .appliedTo(Literal(Constant(len))) + .select(if (exact) defn.Int_== else defn.Int_>=) + .appliedTo(Literal(Constant(0))) + else // try length + scrutinee + .select(defn.Seq_length.matchingMember(scrutinee.tpe)) + .select(if (exact) defn.Int_== else defn.Int_>=) + .appliedTo(Literal(Constant(len))) case TypeTest(tpt) => val expectedTp = tpt.tpe diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 77a38cd78906..0a81b1c0828a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -100,29 +100,34 @@ object Applications { Nil } - def validUnapplySeqType(getTp: Type): Boolean = { - def superType(elemTp: Type) = { - val tps = List( - MethodType(List("len".toTermName))(_ => defn.IntType :: Nil, _ => defn.IntType), - MethodType(List("i".toTermName))(_ => defn.IntType :: Nil, _ => elemTp), - MethodType(List("n".toTermName))(_ => defn.IntType :: Nil, _ => defn.SeqType.appliedTo(elemTp)), - ExprType(defn.SeqType.appliedTo(elemTp)), - ) - val names = List(nme.lengthCompare, nme.apply, nme.drop, nme.toSeq) - RefinedType.make(defn.AnyType, names, tps) - } - getTp <:< superType(WildcardType) && { - val seqArg = extractorMemberType(getTp, nme.toSeq).elemType.hiBound - getTp <:< superType(seqArg) - } + 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 toSeqTp(elemTp: Type) = ExprType(defn.SeqType.appliedTo(elemTp)) + + val elemTp = getTp.member(nme.apply).suchThat(_.info <:< applyTp(WildcardType)).info.resultType + + def names1 = List(nme.lengthCompare, nme.apply, nme.drop, nme.toSeq) + def types1 = List(lengthCompareTp, applyTp(elemTp), dropTp(elemTp), toSeqTp(elemTp)) + + def names2 = List(nme.length, nme.apply, nme.drop, nme.toSeq) + def types2 = List(lengthTp, applyTp(elemTp), dropTp(elemTp), toSeqTp(elemTp)) + + val valid = getTp <:< RefinedType.make(defn.AnyType, names1, types1) || + getTp <:< RefinedType.make(defn.AnyType, names2, types2) + + if (valid) elemTp else NoType } + def validUnapplySeqType(getTp: Type): Boolean = unapplySeqTypeElemTp(getTp).exists + if (unapplyName == nme.unapplySeq) { if (unapplyResult derivesFrom defn.SeqClass) seqSelector :: Nil else if (isGetMatch(unapplyResult, pos) && validUnapplySeqType(getTp)) { - val seqArg = extractorMemberType(getTp, nme.toSeq).elemType.hiBound - if (seqArg.exists) args.map(Function.const(seqArg)) - else fail + val elemTp = unapplySeqTypeElemTp(getTp) + args.map(Function.const(elemTp)) } else fail } diff --git a/tests/run/i4984c.scala b/tests/run/i4984c.scala new file mode 100644 index 000000000000..cd23936ca209 --- /dev/null +++ b/tests/run/i4984c.scala @@ -0,0 +1,32 @@ +object Array2 { + def unapplySeq(x: Array[Int]): Data = new Data + + final class Data { + def isEmpty: Boolean = false + def get: Data = this + def length: Int = 2 + def apply(i: Int): Int = 3 + def drop(n: Int): scala.Seq[Int] = Seq(2, 5) + def toSeq: scala.Seq[Int] = Seq(6, 7) + } +} + +object Test { + def test1(xs: Array[Int]): Int = xs match { + case Array2(x, y) => x + y + } + + def test2(xs: Array[Int]): Seq[Int] = xs match { + case Array2(x, y, xs:_*) => xs + } + + def test3(xs: Array[Int]): Seq[Int] = xs match { + case Array2(xs:_*) => xs + } + + def main(args: Array[String]): Unit = { + test1(Array(3, 5)) + test2(Array(3, 5)) + test3(Array(3, 5)) + } +} From 3b93d98acb286015a95537c1f0f21fd2f7c68c39 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Thu, 6 Sep 2018 00:16:28 +0200 Subject: [PATCH 05/10] add more tests --- tests/run/i4984d.scala | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 tests/run/i4984d.scala diff --git a/tests/run/i4984d.scala b/tests/run/i4984d.scala new file mode 100644 index 000000000000..b53cc8585923 --- /dev/null +++ b/tests/run/i4984d.scala @@ -0,0 +1,35 @@ +object Array2 { + def unapplySeq(x: Array[Int]): Data1 = new Data1 + + class Data1 { + def isEmpty: Boolean = false + def get: Data2 = new Data2 + } + + class Data2 { + def apply(i: Int): Int = 3 + def drop(n: Int): scala.Seq[Int] = Seq(2, 5) + def toSeq: scala.Seq[Int] = Seq(6, 7) + def lengthCompare(len: Int): Int = 0 + } +} + +object Test { + def test1(xs: Array[Int]): Int = xs match { + case Array2(x, y) => x + y + } + + def test2(xs: Array[Int]): Seq[Int] = xs match { + case Array2(x, y, xs:_*) => xs + } + + def test3(xs: Array[Int]): Seq[Int] = xs match { + case Array2(xs:_*) => xs + } + + def main(args: Array[String]): Unit = { + test1(Array(3, 5)) + test2(Array(3, 5)) + test3(Array(3, 5)) + } +} From 9304ed6c12ab2367edfd251949fc4eb58e028c26 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Thu, 6 Sep 2018 00:58:45 +0200 Subject: [PATCH 06/10] fix failed test neg/i3248.scala --- compiler/src/dotty/tools/dotc/typer/Applications.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 0a81b1c0828a..c0442bf4f766 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -115,8 +115,9 @@ object Applications { def names2 = List(nme.length, nme.apply, nme.drop, nme.toSeq) def types2 = List(lengthTp, applyTp(elemTp), dropTp(elemTp), toSeqTp(elemTp)) - val valid = getTp <:< RefinedType.make(defn.AnyType, names1, types1) || - getTp <:< RefinedType.make(defn.AnyType, names2, types2) + val valid = elemTp.exists && + (getTp <:< RefinedType.make(defn.AnyType, names1, types1) || + getTp <:< RefinedType.make(defn.AnyType, names2, types2)) if (valid) elemTp else NoType } From 255c5bb35ddbb4a4320049b5d63cfb65e87682b0 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Thu, 6 Sep 2018 07:57:21 +0200 Subject: [PATCH 07/10] update docs for name-based unapplySeq --- .../dotty/tools/dotc/typer/Applications.scala | 3 +-- .../reference/changed/pattern-matching.md | 26 ++++++++++++++++--- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index c0442bf4f766..dcb43c63e89a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -125,8 +125,7 @@ object Applications { def validUnapplySeqType(getTp: Type): Boolean = unapplySeqTypeElemTp(getTp).exists if (unapplyName == nme.unapplySeq) { - if (unapplyResult derivesFrom defn.SeqClass) seqSelector :: Nil - else if (isGetMatch(unapplyResult, pos) && validUnapplySeqType(getTp)) { + if (isGetMatch(unapplyResult, pos) && validUnapplySeqType(getTp)) { val elemTp = unapplySeqTypeElemTp(getTp) args.map(Function.const(elemTp)) } diff --git a/docs/docs/reference/changed/pattern-matching.md b/docs/docs/reference/changed/pattern-matching.md index 40f5e94201a3..c686a34a26ae 100644 --- a/docs/docs/reference/changed/pattern-matching.md +++ b/docs/docs/reference/changed/pattern-matching.md @@ -63,12 +63,30 @@ object FirstChars { ``` -## Seq Pattern +## Name-based Seq Pattern - Extractor defines `def unapplySeq(x: T): U` - `U` has (parameterless `def` or `val`) members `isEmpty: Boolean` and `get: S` -- `S <: Seq[V]` -- Pattern-matching on `N` pattern with types `V, V, ..., V`, where `N` is the runtime size of the `Seq`. +- `S` conforms to `X` or `Y`, `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 drop(n: Int): scala.Seq[T2] + def toSeq: scala.Seq[T3] +} +``` + +- Pattern-matching on _exactly_ `N` simple patterns with types `T1, T1, ..., T1`, where `N` is the runtime size of the sequence, or +- Pattern-matching on `>= N` simple patterns and _a vararg pattern_ (e.g., `xs: _*`) with types `T1, T1, ..., T1, Seq[T1]`, where `N` is the minimum size of the sequence. @@ -87,7 +105,7 @@ object CharList { ``` -## Name Based Pattern +## Name-based Pattern - Extractor defines `def unapply(x: T): U` - `U` has (parameterless `def` or `val`) members `isEmpty: Boolean` and `get: S` From 94a93e5414a403b78fca61e20ce6724b32ed43ed Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Thu, 6 Sep 2018 13:42:12 +0200 Subject: [PATCH 08/10] address review --- .../tools/dotc/transform/PatternMatcher.scala | 2 +- .../dotty/tools/dotc/typer/Applications.scala | 18 ++++------ tests/run/i4984e.scala | 36 +++++++++++++++++++ 3 files changed, 44 insertions(+), 12 deletions(-) create mode 100644 tests/run/i4984e.scala diff --git a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala index 2e4c8ce1cff5..98f614ae5a18 100644 --- a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -254,7 +254,7 @@ object PatternMatcher { */ def matchElemsPlan(seqSym: Symbol, args: List[Tree], exact: Boolean, onSuccess: Plan) = { val selectors = args.indices.toList.map(idx => - ref(seqSym).select(nme.apply).appliedTo(Literal(Constant(idx)))) + ref(seqSym).select(defn.Seq_apply.matchingMember(seqSym.info)).appliedTo(Literal(Constant(idx)))) TestPlan(LengthTest(args.length, exact), seqSym, seqSym.pos, matchArgsPlan(selectors, args, onSuccess)) } diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index dcb43c63e89a..f7481334ee76 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -109,25 +109,21 @@ object Applications { val elemTp = getTp.member(nme.apply).suchThat(_.info <:< applyTp(WildcardType)).info.resultType - def names1 = List(nme.lengthCompare, nme.apply, nme.drop, nme.toSeq) - def types1 = List(lengthCompareTp, applyTp(elemTp), dropTp(elemTp), toSeqTp(elemTp)) - - def names2 = List(nme.length, nme.apply, nme.drop, nme.toSeq) - def types2 = List(lengthTp, applyTp(elemTp), dropTp(elemTp), toSeqTp(elemTp)) + def test(name: Name, tp: Type) = getTp.member(name).suchThat(_.info <:< tp).exists val valid = elemTp.exists && - (getTp <:< RefinedType.make(defn.AnyType, names1, types1) || - getTp <:< RefinedType.make(defn.AnyType, names2, types2)) + (test(nme.lengthCompare, lengthCompareTp) || test(nme.length, lengthTp)) && + test(nme.drop, dropTp(elemTp)) && + test(nme.toSeq, toSeqTp(elemTp)) if (valid) elemTp else NoType } - def validUnapplySeqType(getTp: Type): Boolean = unapplySeqTypeElemTp(getTp).exists - if (unapplyName == nme.unapplySeq) { - if (isGetMatch(unapplyResult, pos) && validUnapplySeqType(getTp)) { + if (isGetMatch(unapplyResult, pos)) { val elemTp = unapplySeqTypeElemTp(getTp) - args.map(Function.const(elemTp)) + if (elemTp.exists) args.map(Function.const(elemTp)) + else fail } else fail } diff --git a/tests/run/i4984e.scala b/tests/run/i4984e.scala new file mode 100644 index 000000000000..dfc4727f08af --- /dev/null +++ b/tests/run/i4984e.scala @@ -0,0 +1,36 @@ +object Array2 { + def unapplySeq(x: Array[Int]): Data = new Data + + final class Data { + def isEmpty: Boolean = false + def get: Data = this + def lengthCompare(len: Int): Int = 0 + def lengthCompare: Int = 0 + def apply(i: Int): Int = 3 + def apply(i: String): Int = 3 + def drop(n: Int): scala.Seq[Int] = Seq(2, 5) + def drop: scala.Seq[Int] = Seq(2, 5) + def toSeq: scala.Seq[Int] = Seq(6, 7) + def toSeq(x: Int): scala.Seq[Int] = Seq(6, 7) + } +} + +object Test { + def test1(xs: Array[Int]): Int = xs match { + case Array2(x, y) => x + y + } + + def test2(xs: Array[Int]): Seq[Int] = xs match { + case Array2(x, y, xs:_*) => xs + } + + def test3(xs: Array[Int]): Seq[Int] = xs match { + case Array2(xs:_*) => xs + } + + def main(args: Array[String]): Unit = { + test1(Array(3, 5)) + test2(Array(3, 5)) + test3(Array(3, 5)) + } +} From 4d00b9294744e4f199796602de428e0c81604a68 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Thu, 6 Sep 2018 14:58:15 +0200 Subject: [PATCH 09/10] fix ci: member types should go through asSeenFrom --- compiler/src/dotty/tools/dotc/typer/Applications.scala | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index f7481334ee76..4fa3d705e515 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -109,10 +109,12 @@ object Applications { val elemTp = getTp.member(nme.apply).suchThat(_.info <:< applyTp(WildcardType)).info.resultType - def test(name: Name, tp: Type) = getTp.member(name).suchThat(_.info <:< tp).exists + def test(name: Name, tp: Type) = getTp.member(name).suchThat(getTp.memberInfo(_) <:< tp).exists - val valid = elemTp.exists && - (test(nme.lengthCompare, lengthCompareTp) || test(nme.length, lengthTp)) && + val valid = + elemTp.exists && + (test(nme.lengthCompare, lengthCompareTp) || + test(nme.length, lengthTp)) && test(nme.drop, dropTp(elemTp)) && test(nme.toSeq, toSeqTp(elemTp)) From 6a0fc8c60b40e12ac47616f82b7d8b230af16008 Mon Sep 17 00:00:00 2001 From: Allan Renucci Date: Sun, 9 Sep 2018 16:30:25 +0200 Subject: [PATCH 10/10] Cleanup + Documentation + More neg tests --- .../dotty/tools/dotc/typer/Applications.scala | 34 +++++++++++++------ .../reference/changed/pattern-matching.md | 13 ++----- tests/neg/i4984.scala | 30 +++++++++++----- 3 files changed, 47 insertions(+), 30 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 4fa3d705e515..86f1943ab74d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -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) { diff --git a/docs/docs/reference/changed/pattern-matching.md b/docs/docs/reference/changed/pattern-matching.md index c686a34a26ae..3285f0b04ea7 100644 --- a/docs/docs/reference/changed/pattern-matching.md +++ b/docs/docs/reference/changed/pattern-matching.md @@ -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] } diff --git a/tests/neg/i4984.scala b/tests/neg/i4984.scala index 19a236ce462d..fd35940e0731 100644 --- a/tests/neg/i4984.scala +++ b/tests/neg/i4984.scala @@ -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 } }