Skip to content

Commit 9bf5809

Browse files
committed
Change by-name pattern matching.
New implementation following the scheme outlined in #1790.
1 parent ba06bf0 commit 9bf5809

File tree

8 files changed

+95
-58
lines changed

8 files changed

+95
-58
lines changed

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -675,7 +675,9 @@ class Definitions {
675675

676676
private def isVarArityClass(cls: Symbol, prefix: Name) = {
677677
val name = scalaClassName(cls)
678-
name.startsWith(prefix) && name.drop(prefix.length).forall(_.isDigit)
678+
name.startsWith(prefix) &&
679+
name.length > prefix.length &&
680+
name.drop(prefix.length).forall(_.isDigit)
679681
}
680682

681683
def isBottomClass(cls: Symbol) =

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

Lines changed: 27 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -235,14 +235,20 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {
235235
// next: MatchMonad[U]
236236
// returns MatchMonad[U]
237237
def flatMap(prev: Tree, b: Symbol, next: Tree): Tree = {
238-
239-
val getTp = extractorMemberType(prev.tpe, nme.get)
240-
val isDefined = extractorMemberType(prev.tpe, nme.isDefined)
241-
242-
if ((isDefined isRef defn.BooleanClass) && getTp.exists) {
243-
// isDefined and get may be overloaded
244-
val getDenot = prev.tpe.member(nme.get).suchThat(_.info.isParameterless)
245-
val isDefinedDenot = prev.tpe.member(nme.isDefined).suchThat(_.info.isParameterless)
238+
if (isProductMatch(prev.tpe)) {
239+
val nullCheck: Tree = prev.select(defn.Object_ne).appliedTo(Literal(Constant(null)))
240+
ifThenElseZero(
241+
nullCheck,
242+
Block(
243+
List(ValDef(b.asTerm, prev)),
244+
next //Substitution(b, ref(prevSym))(next)
245+
)
246+
)
247+
}
248+
else {
249+
val getDenot = extractorMember(prev.tpe, nme.get)
250+
val isEmptyDenot = extractorMember(prev.tpe, nme.isEmpty)
251+
assert(getDenot.exists && isEmptyDenot.exists, i"${prev.tpe}")
246252

247253
val tmpSym = freshSym(prev.pos, prev.tpe, "o")
248254
val prevValue = ref(tmpSym).select(getDenot.symbol).ensureApplied
@@ -251,20 +257,10 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {
251257
List(ValDef(tmpSym, prev)),
252258
// must be isEmpty and get as we don't control the target of the call (prev is an extractor call)
253259
ifThenElseZero(
254-
ref(tmpSym).select(isDefinedDenot.symbol),
260+
ref(tmpSym).select(isEmptyDenot.symbol).select(defn.Boolean_!),
255261
Block(List(ValDef(b.asTerm, prevValue)), next)
256262
)
257263
)
258-
} else {
259-
assert(defn.isProductSubType(prev.tpe))
260-
val nullCheck: Tree = prev.select(defn.Object_ne).appliedTo(Literal(Constant(null)))
261-
ifThenElseZero(
262-
nullCheck,
263-
Block(
264-
List(ValDef(b.asTerm, prev)),
265-
next //Substitution(b, ref(prevSym))(next)
266-
)
267-
)
268264
}
269265
}
270266

@@ -1431,12 +1427,12 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {
14311427
case _ => or
14321428
}
14331429

1434-
def resultInMonad = if (aligner.isBool) defn.UnitType else {
1435-
val getTp = extractorMemberType(resultType, nme.get)
1436-
if ((extractorMemberType(resultType, nme.isDefined) isRef defn.BooleanClass) && getTp.exists)
1437-
getTp
1430+
def resultInMonad =
1431+
if (aligner.isBool) defn.UnitType
1432+
else if (isProductMatch(resultType)) resultType
1433+
else if (isGetMatch(resultType)) extractorMemberType(resultType, nme.get)
14381434
else resultType
1439-
}
1435+
14401436
def resultType: Type
14411437

14421438
/** Create the TreeMaker that embodies this extractor call
@@ -1632,13 +1628,12 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {
16321628
//val spr = subPatRefs(binder)
16331629
assert(go && go1)
16341630
ref(binder) :: Nil
1635-
} else {
1636-
lazy val getTp = extractorMemberType(binderTypeTested, nme.get)
1637-
if ((aligner.isSingle && aligner.extractor.prodArity == 1) && ((extractorMemberType(binderTypeTested, nme.isDefined) isRef defn.BooleanClass) && getTp.exists))
1638-
List(ref(binder))
1639-
else
1640-
subPatRefs(binder)
16411631
}
1632+
else if ((aligner.isSingle && aligner.extractor.prodArity == 1) &&
1633+
!isProductMatch(binderTypeTested) && isGetMatch(binderTypeTested))
1634+
List(ref(binder))
1635+
else
1636+
subPatRefs(binder)
16421637
}
16431638

16441639
/*protected def spliceApply(binder: Symbol): Tree = {
@@ -1890,9 +1885,8 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {
18901885
else if (result.classSymbol is Flags.CaseClass) result.decls.filter(x => x.is(Flags.CaseAccessor) && x.is(Flags.Method)).map(_.info).toList
18911886
else result.select(nme.get) :: Nil
18921887
)*/
1893-
if ((extractorMemberType(resultType, nme.isDefined) isRef defn.BooleanClass) && resultOfGet.exists)
1894-
getUnapplySelectors(resultOfGet, args)
1895-
else if (defn.isProductSubType(resultType)) productSelectorTypes(resultType)
1888+
if (isProductMatch(resultType)) productSelectorTypes(resultType)
1889+
else if (isGetMatch(resultType)) getUnapplySelectors(resultOfGet, args)
18961890
else if (resultType isRef defn.BooleanClass) Nil
18971891
else {
18981892
ctx.error(i"invalid return type in Unapply node: $resultType")

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

Lines changed: 43 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,37 @@ import reporting.diagnostic.Message
3232
object Applications {
3333
import tpd._
3434

35+
def extractorMember(tp: Type, name: Name)(implicit ctx: Context) = {
36+
def isPossibleExtractorType(tp: Type) = tp match {
37+
case _: MethodType | _: PolyType => false
38+
case _ => true
39+
}
40+
tp.member(name).suchThat(d => isPossibleExtractorType(d.info))
41+
}
42+
3543
def extractorMemberType(tp: Type, name: Name, errorPos: Position = NoPosition)(implicit ctx: Context) = {
36-
val ref = tp.member(name).suchThat(_.info.isParameterless)
44+
val ref = extractorMember(tp, name)
3745
if (ref.isOverloaded)
3846
errorType(i"Overloaded reference to $ref is not allowed in extractor", errorPos)
39-
else if (ref.info.isInstanceOf[PolyType])
40-
errorType(i"Reference to polymorphic $ref: ${ref.info} is not allowed in extractor", errorPos)
41-
else
42-
ref.info.widenExpr.dealias
47+
ref.info.widenExpr.dealias
4348
}
4449

50+
/** Does `tp` fit the "product match" conditions as an unapply result type?
51+
* This is the case of `tp` is a subtype of a ProductN class and `tp` has a
52+
* parameterless `isDefined` member of result type `Boolean`.
53+
*/
54+
def isProductMatch(tp: Type, errorPos: Position = NoPosition)(implicit ctx: Context) =
55+
extractorMemberType(tp, nme.isDefined, errorPos).isRef(defn.BooleanClass) &&
56+
defn.isProductSubType(tp)
57+
58+
/** Does `tp` fit the "get match" conditions as an unapply result type?
59+
* This is the case of `tp` has a `get` member as well as a
60+
* parameterless `isDefined` member of result type `Boolean`.
61+
*/
62+
def isGetMatch(tp: Type, errorPos: Position = NoPosition)(implicit ctx: Context) =
63+
extractorMemberType(tp, nme.isEmpty, errorPos).isRef(defn.BooleanClass) &&
64+
extractorMemberType(tp, nme.get, errorPos).exists
65+
4566
def productSelectorTypes(tp: Type, errorPos: Position = NoPosition)(implicit ctx: Context): List[Type] = {
4667
val sels = for (n <- Iterator.from(0)) yield extractorMemberType(tp, nme.selectorName(n), errorPos)
4768
sels.takeWhile(_.exists).toList
@@ -62,24 +83,27 @@ object Applications {
6283
def unapplyArgs(unapplyResult: Type, unapplyFn: Tree, args: List[untpd.Tree], pos: Position = NoPosition)(implicit ctx: Context): List[Type] = {
6384

6485
def seqSelector = defn.RepeatedParamType.appliedTo(unapplyResult.elemType :: Nil)
65-
def getTp = extractorMemberType(unapplyResult, nme.get, pos)
6686

67-
// println(s"unapply $unapplyResult ${extractorMemberType(unapplyResult, nme.isDefined)}")
68-
if (extractorMemberType(unapplyResult, nme.isDefined, pos) isRef defn.BooleanClass) {
69-
if (getTp.exists)
70-
if (unapplyFn.symbol.name == nme.unapplySeq) {
71-
val seqArg = boundsToHi(getTp.elemType)
72-
if (seqArg.exists) return args map Function.const(seqArg)
73-
}
74-
else return getUnapplySelectors(getTp, args, pos)
75-
else if (defn.isProductSubType(unapplyResult)) return productSelectorTypes(unapplyResult, pos)
76-
}
77-
if (unapplyResult derivesFrom defn.SeqClass) seqSelector :: Nil
78-
else if (unapplyResult isRef defn.BooleanClass) Nil
79-
else {
87+
def fail = {
8088
ctx.error(i"$unapplyResult is not a valid result type of an unapply method of an extractor", pos)
8189
Nil
8290
}
91+
92+
// println(s"unapply $unapplyResult ${extractorMemberType(unapplyResult, nme.isDefined)}")
93+
if (isProductMatch(unapplyResult))
94+
productSelectorTypes(unapplyResult)
95+
else if (isGetMatch(unapplyResult)) {
96+
val getTp = extractorMemberType(unapplyResult, nme.get, pos)
97+
if (unapplyFn.symbol.name == nme.unapplySeq) {
98+
val seqArg = boundsToHi(getTp.elemType)
99+
if (seqArg.exists) args.map(Function.const(seqArg))
100+
else fail
101+
}
102+
else getUnapplySelectors(getTp, args, pos)
103+
}
104+
else if (unapplyResult derivesFrom defn.SeqClass) seqSelector :: Nil
105+
else if (unapplyResult isRef defn.BooleanClass) Nil
106+
else fail
83107
}
84108

85109
def wrapDefs(defs: mutable.ListBuffer[Tree], tree: Tree)(implicit ctx: Context): Tree =

compiler/test/dotc/scala-collections.whitelist

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,3 +280,5 @@
280280
../scala-scala/src/library/scala/collection/generic/Subtractable.scala
281281
../scala-scala/src/library/scala/collection/generic/TraversableFactory.scala
282282
../scala-scala/src/library/scala/collection/generic/package.scala
283+
284+
../scala-scala/src/library/scala/util/Try.scala

tests/pos/i1540.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
class Casey1(val a: Int) {
2-
def isDefined: Boolean = true
3-
def isDefined(x: Int): Boolean = ???
2+
def isEmpty: Boolean = false
3+
def isEmpty(x: Int): Boolean = ???
44
def get: Int = a
55
def get(x: Int): String = ???
66
}

tests/pos/i1540b.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
class Casey1[T](val a: T) {
2-
def isDefined: Boolean = true
3-
def isDefined(x: T): Boolean = ???
2+
def isEmpty: Boolean = false
3+
def isEmpty(x: T): Boolean = ???
44
def get: T = a
55
def get(x: T): String = ???
66
}

tests/pos/i1790.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import scala.util.control.NonFatal
2+
3+
class Try[+T] {
4+
def transform[U](s: T => Try[U], f: Throwable => Try[U]): Try[U] =
5+
try this match {
6+
case Success(v) => s(v)
7+
case Failure(e) => f(e)
8+
} catch {
9+
case NonFatal(e) => Failure(e)
10+
}
11+
}
12+
final case class Success[+T](value: T) extends Try[T]
13+
final case class Failure[+T](exception: Throwable) extends Try[T] {
14+
def get: T = throw exception
15+
}

tests/pos/pos_valueclasses/optmatch.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ package optmatch
77

88
class NonZeroLong(val value: Long) extends AnyVal {
99
def get: Long = value
10-
def isDefined: Boolean = get != 0l
10+
def isEmpty: Boolean = get == 0l
1111
}
1212
object NonZeroLong {
1313
def unapply(value: Long): NonZeroLong = new NonZeroLong(value)

0 commit comments

Comments
 (0)