Skip to content

Commit 944e677

Browse files
Decouple Product and pattern-matching
Product pattern use to: - have a `<: Product` requirement - compute the arity of a pattern by looking at `N` in a `ProductN` superclass. This commit changes `<: Product`, instead we look for a `_1` member. The arity is determined by inspecting `_1` to `_N` members instead. --- Here another attempt to formalize Dotty's pattern-matching (base on #1805). This covers the *Extractor Patterns* [section of the spec](https://www.scala-lang.org/files/archive/spec/2.12/08-pattern-matching.html#extractor-patterns). Dotty support 4 different extractor patterns: Boolean Pattern, Product Pattern, Seq Pattern and Name Based Pattern. Boolean Pattern - Extractor defines `def unapply(x: T): Boolean` - Pattern-matching on exactly `0` patterns Product Pattern - Extractor defines `def unapply(x: T): U` - `N > 0` is the maximum number of consecutive (parameterless `def` or `val`) `_1: P1` ... `_N: PN` members in `U` - Pattern-matching on exactly `N` patterns with types `P1, P2, ..., PN` 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 any number of pattern with types `V, V, ..., V` Name Based Pattern - Extractor defines `def unapply(x: T): U` - `U` has (parameterless `def` or `val`) members `isEmpty: Boolean` and `get: S` - If there is exactly `1` pattern, pattern-matching on `1` pattern with type `S` - Otherwise fallback to Product Pattern on type `U` In case of ambiguities, *Product Pattern* is preferred over *Name Based Pattern*.
1 parent c0ff8ad commit 944e677

File tree

4 files changed

+13
-18
lines changed

4 files changed

+13
-18
lines changed

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import scala.collection.{ mutable, immutable }
1010
import PartialFunction._
1111
import collection.mutable
1212
import util.common.alwaysZero
13+
import typer.Applications
1314

1415
object Definitions {
1516

@@ -847,17 +848,9 @@ class Definitions {
847848
}
848849

849850
def isProductSubType(tp: Type)(implicit ctx: Context) =
850-
(tp derivesFrom ProductType.symbol) && tp.baseClasses.exists(isProductClass)
851+
Applications.extractorMemberType(tp, nme._1).exists
851852

852-
def productArity(tp: Type)(implicit ctx: Context) =
853-
if (tp derivesFrom ProductType.symbol)
854-
tp.baseClasses.find(isProductClass) match {
855-
case Some(prod) => prod.typeParams.length
856-
case None => -1
857-
}
858-
else -1
859-
860-
/** Is `tp` (an alias) of either a scala.FunctionN or a scala.ImplicitFunctionN ? */
853+
/** Is `tp` (an alias) of either a scala.FunctionN or a scala.ImplicitFunctionN? */
861854
def isFunctionType(tp: Type)(implicit ctx: Context) = {
862855
val arity = functionArity(tp)
863856
val sym = tp.dealias.typeSymbol

compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {
233233
// next: MatchMonad[U]
234234
// returns MatchMonad[U]
235235
def flatMap(prev: Tree, b: Symbol, next: Tree): Tree = {
236-
val resultArity = defn.productArity(b.info)
236+
val resultArity = productArity(b.info)
237237
if (isProductMatch(prev.tpe, resultArity)) {
238238
val nullCheck: Tree = prev.select(defn.Object_ne).appliedTo(Literal(Constant(null)))
239239
ifThenElseZero(

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,12 @@ object Applications {
4848
}
4949

5050
/** Does `tp` fit the "product match" conditions as an unapply result type
51-
* for a pattern with `numArgs` subpatterns>
52-
* This is the case of `tp` is a subtype of the Product<numArgs> class.
51+
* for a pattern with `numArgs` subpatterns?
52+
* This is the case of `tp` has members `_1` to `_N` where `N == numArgs`.
5353
*/
5454
def isProductMatch(tp: Type, numArgs: Int)(implicit ctx: Context) =
55-
0 <= numArgs && numArgs <= Definitions.MaxTupleArity &&
56-
tp.derivesFrom(defn.ProductNType(numArgs).typeSymbol)
55+
numArgs > 0 && defn.isProductSubType(tp) &&
56+
productSelectorTypes(tp).size == numArgs
5757

5858
/** Does `tp` fit the "get match" conditions as an unapply result type?
5959
* This is the case of `tp` has a `get` member as well as a
@@ -68,6 +68,9 @@ object Applications {
6868
sels.takeWhile(_.exists).toList
6969
}
7070

71+
def productArity(tp: Type)(implicit ctx: Context) =
72+
if (defn.isProductSubType(tp)) productSelectorTypes(tp).size else -1
73+
7174
def productSelectors(tp: Type)(implicit ctx: Context): List[Symbol] = {
7275
val sels = for (n <- Iterator.from(0)) yield tp.member(nme.selectorName(n)).symbol
7376
sels.takeWhile(_.exists).toList

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -761,10 +761,9 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
761761

762762
/** Is `formal` a product type which is elementwise compatible with `params`? */
763763
def ptIsCorrectProduct(formal: Type) = {
764-
val pclass = defn.ProductNType(params.length).symbol
765764
isFullyDefined(formal, ForceDegree.noBottom) &&
766-
formal.derivesFrom(pclass) &&
767-
formal.baseArgTypes(pclass).corresponds(params) {
765+
defn.isProductSubType(formal) &&
766+
Applications.productSelectorTypes(formal).corresponds(params) {
768767
(argType, param) =>
769768
param.tpt.isEmpty || argType <:< typedAheadType(param.tpt).tpe
770769
}

0 commit comments

Comments
 (0)