Skip to content
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

Fix #4984: support name-based unapplySeq #5078

Merged
merged 10 commits into from
Sep 20, 2018
Merged

Conversation

liufengyun
Copy link
Contributor

Fix #4984: support name-based unapplySeq

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)),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean that the argument name must conform to len, i and n?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Param names are not meaningful in comparing method types.

val names = List(nme.lengthCompare, nme.apply, nme.drop, nme.toSeq)
RefinedType.make(defn.AnyType, names, tps)
}
getTp <:< superType(WildcardType) && {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this test?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, we can simplify by removing the first test.

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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scalac require lengthCompare or length. If there's no lengthCompare, the translation uses length

And we should add tests for cases where we only have one or the other

RefinedType.make(defn.AnyType, names, tps)
}
getTp <:< superType(WildcardType) && {
val seqArg = extractorMemberType(getTp, nme.toSeq).elemType.hiBound
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use the result type of apply like scalac

@@ -100,10 +100,27 @@ object Applications {
Nil
}

def validUnapplySeqType(getTp: Type): Boolean = {
Copy link
Contributor

@allanrenucci allanrenucci Sep 5, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am wondering if we should do something along the line of (inspired by implementation of isGetMatch):

  def isValidUnapplySeqType(tp: Type, errorPos: Position)(implicit ctx: Context) =
    isGetMatch(tp, errorPos) && {
      val getTpe = extractorMemberType(tp, nme.get, errorPos)
      val applyTpe = extractorMemberType(getTpe, nme.apply, errorPos)
      applyTpe.exists && {
        def dropTpe = extractorMemberType(getTpe, nme.applyTpe, errorPos)
        def toSeqTpe = extractorMemberType(getTpe, nme.toSeq, errorPos)
        def lengthCompareTpe = extractorMemberType(getTpe, nme.lengthCompare, errorPos)
        val lengthTpe = extractorMemberType(getTpe, nme.length, errorPos)

        (lengthCompareTpe.isRef(defn.BooleanClass) || lengthTpe.isRef(defn.IntClass)) && {
          val expectedSeqTpe = defn.SeqType.appliedTo(applyTpe)
          dropTpe <:< expectedSeqTpe && toSeqTpe <:< expectedSeqTpe
        }
      }
    }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original implementation is a concise way to implement the same logic. In the above, extractorMemberType doesn't work for methods with parameters, and we will need to check both NoType and param types of the methods.

object Array2 {
def unapplySeq(x: Array[Int]): Data = new Data

final class Data {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we also a add test with:

class Data1 {
    def isEmpty: Boolean = false
    def get: Data2 = new Data2
    def lengthCompare(len: Int): Int = 0
  }
}

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)
}

patternPlan(getResult, arg, onSuccess)
if (args.length == 1) {
val toSeq = ref(getResult)
.select(defn.Seq_toSeq.matchingMember(getResult.info))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not simply: .select(nme.toSeq)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code is defensive here to support possible overloading of toSeq.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add tests for this then. Would it work to do .select(nme.toSeq).appliedToNone()?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My hunch is that it will crash the compiler, as appliedToNone will not do overloading resolution.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But there is no need right? You cannot have two overloads of a method that takes no parameter

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Never mind, your solution seems better. matchingMember is already used in multiple places in this file (I suppose for reasons)

def types2 = List(lengthTp, applyTp(elemTp), dropTp(elemTp), toSeqTp(elemTp))

val valid = elemTp.exists &&
(getTp <:< RefinedType.make(defn.AnyType, names1, types1) ||
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a rather complicated (and probably inefficient) way of checking if a type has a given member, why not use extractorMemberType like in other places in this file?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extractorMemberType only checks the existence of a parameterless method, here we need to check method types for conformance. If we extract the members one by one and check param & return type, the code would be tedious.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @smarter. It seems inefficient. In particular the second subtype test getTp <:< RefinedType.make(defn.AnyType, names2, types2)) will do a lot of unnecessary work that has already been done in the previous subtype test (i.e. checking members apply, drop and toSeq).

I think the right ways is to perform members lookup even if it is more verbose

@smarter
Copy link
Member

smarter commented Sep 6, 2018

Before we forget, it'd be good to update the spec for name-based pattern matching: https://github.com/lampepfl/dotty/blob/master/docs/docs/reference/changed/pattern-matching.md#name-based-pattern

Copy link
Contributor

@allanrenucci allanrenucci left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also add test cases with overloaded versions of lengthCompare, apply, drop and toSeq

if (unapplyName == nme.unapplySeq) {
if (unapplyResult derivesFrom defn.SeqClass) seqSelector :: Nil
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this case removed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There seems to be no spec for this, I've no idea what it is intended for.

if (seqArg.exists) args.map(Function.const(seqArg))
else fail
if (isGetMatch(unapplyResult, pos) && validUnapplySeqType(getTp)) {
val elemTp = unapplySeqTypeElemTp(getTp)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unapplySeqTypeElemTp is also called by validUnapplySeqType and seems very expensive. There is no need to call it twice. We could rewrite to something like:

if (isGetMatch(unapplyResult, pos)) {
  val elemTp = unapplySeqTypeElemTp(getTp)
  if (elemTp.exists) ...
  else fail
}
else fail

def types2 = List(lengthTp, applyTp(elemTp), dropTp(elemTp), toSeqTp(elemTp))

val valid = elemTp.exists &&
(getTp <:< RefinedType.make(defn.AnyType, names1, types1) ||
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @smarter. It seems inefficient. In particular the second subtype test getTp <:< RefinedType.make(defn.AnyType, names2, types2)) will do a lot of unnecessary work that has already been done in the previous subtype test (i.e. checking members apply, drop and toSeq).

I think the right ways is to perform members lookup even if it is more verbose

Copy link
Contributor

@allanrenucci allanrenucci left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Maybe @odersky should have a final look

@odersky odersky merged commit a2a1112 into scala:master Sep 20, 2018
@liufengyun liufengyun deleted the fix-4984 branch September 20, 2018 13:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants