aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFelix Mulder <felix.mulder@gmail.com>2017-04-11 12:01:17 +0200
committerGitHub <noreply@github.com>2017-04-11 12:01:17 +0200
commit4868fb29580a67c7a1560d5c1c7cc658e2634359 (patch)
tree1deed7fb92912f09b810e905e351fe4cd796cc6b
parent579571e05a08120133173933e7eaf2555846d1d7 (diff)
parent198b5cec531a8e0d6c121cc425e19a54b7818868 (diff)
downloaddotty-4868fb29580a67c7a1560d5c1c7cc658e2634359.tar.gz
dotty-4868fb29580a67c7a1560d5c1c7cc658e2634359.tar.bz2
dotty-4868fb29580a67c7a1560d5c1c7cc658e2634359.zip
Merge pull request #1938 from dotty-staging/named-based-patmat
Change case class desugaring and decouple Products and name-based-pattern-matching
-rw-r--r--.gitignore2
-rw-r--r--compiler/src/dotty/tools/dotc/ast/Desugar.scala83
-rw-r--r--compiler/src/dotty/tools/dotc/core/Definitions.scala15
-rw-r--r--compiler/src/dotty/tools/dotc/repl/ammonite/Protocol.scala2
-rw-r--r--compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala4
-rw-r--r--compiler/src/dotty/tools/dotc/typer/Applications.scala15
-rw-r--r--compiler/src/dotty/tools/dotc/typer/Typer.scala5
-rw-r--r--tests/pos/t7296.scala (renamed from tests/pending/pos/t7296.scala)0
-rw-r--r--tests/run/1938.scala45
-rw-r--r--tests/run/case-class-23.check (renamed from tests/pending/run/case-class-23.check)0
-rw-r--r--tests/run/case-class-23.scala (renamed from tests/pending/run/case-class-23.scala)0
-rw-r--r--tests/run/double-pattern-type.scala40
-rw-r--r--tests/run/zero-arity-case-class.scala26
13 files changed, 186 insertions, 51 deletions
diff --git a/.gitignore b/.gitignore
index 170cf4823..7508b4932 100644
--- a/.gitignore
+++ b/.gitignore
@@ -55,3 +55,5 @@ testlogs/
local/
compiler/test/debug/Gen.jar
+compiler/before-pickling.txt
+compiler/after-pickling.txt
diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala
index 111382b18..2722d91eb 100644
--- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala
+++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala
@@ -353,8 +353,6 @@ object desugar {
lazy val creatorExpr = New(classTypeRef, constrVparamss nestedMap refOfDef)
// Methods to add to a case class C[..](p1: T1, ..., pN: Tn)(moreParams)
- // def isDefined = true
- // def productArity = N
// def _1 = this.p1
// ...
// def _N = this.pN
@@ -362,17 +360,38 @@ object desugar {
// pN: TN = pN: @uncheckedVariance)(moreParams) =
// new C[...](p1, ..., pN)(moreParams)
//
+ // Above arity 22 we also synthesize:
+ // def productArity = N
+ // def productElement(i: Int): Any = i match { ... }
+ //
// Note: copy default parameters need @uncheckedVariance; see
// neg/t1843-variances.scala for a test case. The test would give
// two errors without @uncheckedVariance, one of them spurious.
- val caseClassMeths =
- if (isCaseClass) {
- def syntheticProperty(name: TermName, rhs: Tree) =
- DefDef(name, Nil, Nil, TypeTree(), rhs).withMods(synthetic)
+ val caseClassMeths = {
+ def syntheticProperty(name: TermName, rhs: Tree) =
+ DefDef(name, Nil, Nil, TypeTree(), rhs).withMods(synthetic)
+ def productArity = syntheticProperty(nme.productArity, Literal(Constant(arity)))
+ def productElement = {
+ val param = makeSyntheticParameter(tpt = ref(defn.IntType))
+ // case N => _${N + 1}
+ val cases = 0.until(arity).map { i =>
+ CaseDef(Literal(Constant(i)), EmptyTree, Select(This(EmptyTypeIdent), nme.selectorName(i)))
+ }
+ val ioob = ref(defn.IndexOutOfBoundsException.typeRef)
+ val error = Throw(New(ioob, List(List(Select(refOfDef(param), nme.toString_)))))
+ // case _ => throw new IndexOutOfBoundsException(i.toString)
+ val defaultCase = CaseDef(untpd.Ident(nme.WILDCARD), EmptyTree, error)
+ val body = Match(refOfDef(param), (cases :+ defaultCase).toList)
+ DefDef(nme.productElement, Nil, List(List(param)), TypeTree(defn.AnyType), body)
+ .withMods(synthetic)
+ }
+ def productElemMeths = {
val caseParams = constrVparamss.head.toArray
- val productElemMeths =
- for (i <- 0 until arity if nme.selectorName(i) `ne` caseParams(i).name)
- yield syntheticProperty(nme.selectorName(i), Select(This(EmptyTypeIdent), caseParams(i).name))
+ for (i <- 0 until arity if nme.selectorName(i) `ne` caseParams(i).name)
+ yield syntheticProperty(nme.selectorName(i), Select(This(EmptyTypeIdent), caseParams(i).name))
+ }
+ def enumTagMeths = if (isEnumCase) enumTagMeth(CaseKind.Class)._1 :: Nil else Nil
+ def copyMeths = {
def isRepeated(tree: Tree): Boolean = tree match {
case PostfixOp(_, Ident(nme.raw.STAR)) => true
case ByNameTypeTree(tree1) => isRepeated(tree1)
@@ -382,24 +401,29 @@ object desugar {
case ValDef(_, tpt, _) => isRepeated(tpt)
case _ => false
})
-
- val copyMeths =
- if (mods.is(Abstract) || hasRepeatedParam) Nil // cannot have default arguments for repeated parameters, hence copy method is not issued
- else {
- def copyDefault(vparam: ValDef) =
- makeAnnotated("scala.annotation.unchecked.uncheckedVariance", refOfDef(vparam))
- val copyFirstParams = derivedVparamss.head.map(vparam =>
- cpy.ValDef(vparam)(rhs = copyDefault(vparam)))
- val copyRestParamss = derivedVparamss.tail.nestedMap(vparam =>
- cpy.ValDef(vparam)(rhs = EmptyTree))
- DefDef(nme.copy, derivedTparams, copyFirstParams :: copyRestParamss, TypeTree(), creatorExpr)
- .withMods(synthetic) :: Nil
- }
-
- val enumTagMeths = if (isEnumCase) enumTagMeth(CaseKind.Class)._1 :: Nil else Nil
- copyMeths ::: enumTagMeths ::: productElemMeths.toList
+ if (mods.is(Abstract) || hasRepeatedParam) Nil // cannot have default arguments for repeated parameters, hence copy method is not issued
+ else {
+ def copyDefault(vparam: ValDef) =
+ makeAnnotated("scala.annotation.unchecked.uncheckedVariance", refOfDef(vparam))
+ val copyFirstParams = derivedVparamss.head.map(vparam =>
+ cpy.ValDef(vparam)(rhs = copyDefault(vparam)))
+ val copyRestParamss = derivedVparamss.tail.nestedMap(vparam =>
+ cpy.ValDef(vparam)(rhs = EmptyTree))
+ DefDef(nme.copy, derivedTparams, copyFirstParams :: copyRestParamss, TypeTree(), creatorExpr)
+ .withMods(synthetic) :: Nil
+ }
}
+
+ // Above MaxTupleArity we extend Product instead of ProductN, in this
+ // case we need to synthesise productElement & productArity.
+ def largeProductMeths =
+ if (arity > Definitions.MaxTupleArity) productElement :: productArity :: Nil
+ else Nil
+
+ if (isCaseClass)
+ largeProductMeths ::: copyMeths ::: enumTagMeths ::: productElemMeths.toList
else Nil
+ }
def anyRef = ref(defn.AnyRefAlias.typeRef)
def productConstr(n: Int) = {
@@ -407,13 +431,16 @@ object desugar {
val targs = constrVparamss.head map (_.tpt)
if (targs.isEmpty) tycon else AppliedTypeTree(tycon, targs)
}
+ def product =
+ if (arity > Definitions.MaxTupleArity) scalaDot(nme.Product.toTypeName)
+ else productConstr(arity)
- // Case classes and case objects get a ProductN parent
+ // Case classes and case objects get Product/ProductN parents
var parents1 = parents
if (isEnumCase && parents.isEmpty)
parents1 = enumClassTypeRef :: Nil
- if (mods.is(Case) && arity <= Definitions.MaxTupleArity)
- parents1 = parents1 :+ productConstr(arity) // TODO: This also adds Product0 to caes objects. Do we want that?
+ if (mods.is(Case))
+ parents1 = parents1 :+ product // TODO: This also adds Product0 to case objects. Do we want that?
if (isEnum)
parents1 = parents1 :+ ref(defn.EnumType)
diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala
index c7b1538c7..a97589d73 100644
--- a/compiler/src/dotty/tools/dotc/core/Definitions.scala
+++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala
@@ -10,6 +10,7 @@ import scala.collection.{ mutable, immutable }
import PartialFunction._
import collection.mutable
import util.common.alwaysZero
+import typer.Applications
object Definitions {
@@ -477,6 +478,7 @@ class Definitions {
lazy val JavaCloneableClass = ctx.requiredClass("java.lang.Cloneable")
lazy val NullPointerExceptionClass = ctx.requiredClass("java.lang.NullPointerException")
+ lazy val IndexOutOfBoundsException = ctx.requiredClass("java.lang.IndexOutOfBoundsException")
lazy val ClassClass = ctx.requiredClass("java.lang.Class")
lazy val BoxedNumberClass = ctx.requiredClass("java.lang.Number")
lazy val ThrowableClass = ctx.requiredClass("java.lang.Throwable")
@@ -844,18 +846,7 @@ class Definitions {
TupleType(elems.size).appliedTo(elems)
}
- def isProductSubType(tp: Type)(implicit ctx: Context) =
- (tp derivesFrom ProductType.symbol) && tp.baseClasses.exists(isProductClass)
-
- def productArity(tp: Type)(implicit ctx: Context) =
- if (tp derivesFrom ProductType.symbol)
- tp.baseClasses.find(isProductClass) match {
- case Some(prod) => prod.typeParams.length
- case None => -1
- }
- else -1
-
- /** Is `tp` (an alias) of either a scala.FunctionN or a scala.ImplicitFunctionN ? */
+ /** Is `tp` (an alias) of either a scala.FunctionN or a scala.ImplicitFunctionN? */
def isFunctionType(tp: Type)(implicit ctx: Context) = {
val arity = functionArity(tp)
val sym = tp.dealias.typeSymbol
diff --git a/compiler/src/dotty/tools/dotc/repl/ammonite/Protocol.scala b/compiler/src/dotty/tools/dotc/repl/ammonite/Protocol.scala
index 34d31aeca..e066050b6 100644
--- a/compiler/src/dotty/tools/dotc/repl/ammonite/Protocol.scala
+++ b/compiler/src/dotty/tools/dotc/repl/ammonite/Protocol.scala
@@ -5,7 +5,7 @@ package ammonite.terminal
case class TermInfo(ts: TermState, width: Int)
-sealed trait TermAction
+trait TermAction
case class Printing(ts: TermState, stdout: String) extends TermAction
case class TermState(
inputs: LazyList[Int],
diff --git a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala
index d87412d93..41a1218eb 100644
--- a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala
+++ b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala
@@ -233,7 +233,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {
// next: MatchMonad[U]
// returns MatchMonad[U]
def flatMap(prev: Tree, b: Symbol, next: Tree): Tree = {
- val resultArity = defn.productArity(b.info)
+ val resultArity = productArity(b.info)
if (isProductMatch(prev.tpe, resultArity)) {
val nullCheck: Tree = prev.select(defn.Object_ne).appliedTo(Literal(Constant(null)))
ifThenElseZero(
@@ -1408,7 +1408,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {
protected def seqTree(binder: Symbol) = tupleSel(binder)(firstIndexingBinder + 1)
protected def tupleSel(binder: Symbol)(i: Int): Tree = {
val accessors =
- if (defn.isProductSubType(binder.info))
+ if (Applications.canProductMatch(binder.info))
productSelectors(binder.info)
else binder.caseAccessors
val res =
diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala
index 2f2af9868..c4d3e2292 100644
--- a/compiler/src/dotty/tools/dotc/typer/Applications.scala
+++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala
@@ -48,13 +48,15 @@ object Applications {
ref.info.widenExpr.dealias
}
+ def canProductMatch(tp: Type)(implicit ctx: Context) =
+ extractorMemberType(tp, nme._1).exists
+
/** Does `tp` fit the "product match" conditions as an unapply result type
- * for a pattern with `numArgs` subpatterns>
- * This is the case of `tp` is a subtype of the Product<numArgs> class.
+ * for a pattern with `numArgs` subpatterns?
+ * This is the case of `tp` has members `_1` to `_N` where `N == numArgs`.
*/
def isProductMatch(tp: Type, numArgs: Int)(implicit ctx: Context) =
- 0 <= numArgs && numArgs <= Definitions.MaxTupleArity &&
- tp.derivesFrom(defn.ProductNType(numArgs).typeSymbol)
+ numArgs > 0 && productArity(tp) == numArgs
/** Does `tp` fit the "get match" conditions as an unapply result type?
* This is the case of `tp` has a `get` member as well as a
@@ -69,6 +71,9 @@ object Applications {
sels.takeWhile(_.exists).toList
}
+ def productArity(tp: Type)(implicit ctx: Context) =
+ if (canProductMatch(tp)) productSelectorTypes(tp).size else -1
+
def productSelectors(tp: Type)(implicit ctx: Context): List[Symbol] = {
val sels = for (n <- Iterator.from(0)) yield tp.member(nme.selectorName(n)).symbol
sels.takeWhile(_.exists).toList
@@ -109,7 +114,7 @@ object Applications {
getUnapplySelectors(getTp, args, pos)
else if (unapplyResult isRef defn.BooleanClass)
Nil
- else if (defn.isProductSubType(unapplyResult))
+ else if (canProductMatch(unapplyResult))
productSelectorTypes(unapplyResult)
// this will cause a "wrong number of arguments in pattern" error later on,
// which is better than the message in `fail`.
diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala
index e6236d122..ba55dfe30 100644
--- a/compiler/src/dotty/tools/dotc/typer/Typer.scala
+++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala
@@ -762,10 +762,9 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
/** Is `formal` a product type which is elementwise compatible with `params`? */
def ptIsCorrectProduct(formal: Type) = {
- val pclass = defn.ProductNType(params.length).symbol
isFullyDefined(formal, ForceDegree.noBottom) &&
- formal.derivesFrom(pclass) &&
- formal.baseArgTypes(pclass).corresponds(params) {
+ Applications.canProductMatch(formal) &&
+ Applications.productSelectorTypes(formal).corresponds(params) {
(argType, param) =>
param.tpt.isEmpty || argType <:< typedAheadType(param.tpt).tpe
}
diff --git a/tests/pending/pos/t7296.scala b/tests/pos/t7296.scala
index fcba17c08..fcba17c08 100644
--- a/tests/pending/pos/t7296.scala
+++ b/tests/pos/t7296.scala
diff --git a/tests/run/1938.scala b/tests/run/1938.scala
new file mode 100644
index 000000000..95e94678d
--- /dev/null
+++ b/tests/run/1938.scala
@@ -0,0 +1,45 @@
+case class Large(
+ e1: Int,
+ e2: Int,
+ e3: Int,
+ e4: Int,
+ e5: Int,
+ e6: Int,
+ e7: Int,
+ e8: Int,
+ e9: Int,
+ e10: Int,
+ e11: Int,
+ e12: Int,
+ e13: Int,
+ e14: Int,
+ e15: Int,
+ e16: Int,
+ e17: Int,
+ e18: Int,
+ e19: Int,
+ e20: Int,
+ e21: Int,
+ e22: Int,
+ e23: Int
+)
+
+object Test {
+ def main(args: Array[String]): Unit = {
+ val l = Large(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23)
+
+ assert(l.productArity == 23)
+
+ assert(l.productElement(0) == 1)
+ assert(l.productElement(1) == 2)
+ assert(l.productElement(21) == 22)
+ assert(l.productElement(22) == 23)
+
+ try {
+ l.productElement(23)
+ ???
+ } catch {
+ case e: IndexOutOfBoundsException => assert(e.getMessage == "23")
+ }
+ }
+}
diff --git a/tests/pending/run/case-class-23.check b/tests/run/case-class-23.check
index 888ed2c9e..888ed2c9e 100644
--- a/tests/pending/run/case-class-23.check
+++ b/tests/run/case-class-23.check
diff --git a/tests/pending/run/case-class-23.scala b/tests/run/case-class-23.scala
index a6d78763c..a6d78763c 100644
--- a/tests/pending/run/case-class-23.scala
+++ b/tests/run/case-class-23.scala
diff --git a/tests/run/double-pattern-type.scala b/tests/run/double-pattern-type.scala
new file mode 100644
index 000000000..8045d173b
--- /dev/null
+++ b/tests/run/double-pattern-type.scala
@@ -0,0 +1,40 @@
+case class C1(i: String, s: Int) { def isEmpty = false; def get = ("EMPTY", -1) }
+case class C2(i: String, s: String) { def isEmpty = false; def get = (-1, -2, -3) }
+
+object Test {
+ def main(args: Array[String]): Unit = {
+ // When both Product and name based patterns with same arity are available,
+ // we follow scalac and silently use the Product one:
+
+ val c1 = C1("s", 0)
+ c1 match {
+ case C1(a, b) =>
+ assert(a == "s")
+ assert(b == 0)
+ }
+
+ // When the size differ, both are patterns become usable:
+
+ val c2 = C2("a", "b")
+ c2 match {
+ case C2(a, b) =>
+ assert(a == "a")
+ assert(b == "b")
+ }
+
+ c2 match {
+ case C2(a, b, c) =>
+ assert(a == -1)
+ assert(b == -2)
+ assert(c == -3)
+ }
+
+ // Interestingly things also compile with a single pattern, in which case
+ // the tuple returned by get is binded to `a`:
+
+ c2 match {
+ case C2(a) =>
+ assert(a == (-1, -2, -3))
+ }
+ }
+}
diff --git a/tests/run/zero-arity-case-class.scala b/tests/run/zero-arity-case-class.scala
new file mode 100644
index 000000000..de0ba4fe1
--- /dev/null
+++ b/tests/run/zero-arity-case-class.scala
@@ -0,0 +1,26 @@
+case class Foo()
+
+object Test {
+ def main(args: Array[String]): Unit = {
+ assert(Foo.unapply(Foo()) == true)
+
+ // unapply generate by scalac are `_ != null`,
+ // dotty returns true in all cases
+ assert(Foo.unapply(null) == true)
+
+ Foo() match {
+ case Foo() => ()
+ case _ => ???
+ }
+
+ Foo() match {
+ case _: Foo => ()
+ case _ => ???
+ }
+
+ (Foo(): Any) match {
+ case Foo() => ()
+ case _ => ???
+ }
+ }
+}