-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Fix #4984: support name-based unapplySeq #5078
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
Conversation
| 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)), |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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) && { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does this test?
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
| Nil | ||
| } | ||
|
|
||
| def validUnapplySeqType(getTp: Type): Boolean = { |
There was a problem hiding this comment.
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
}
}
}There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
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)?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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()?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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) || |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
|
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 |
allanrenucci
left a comment
There was a problem hiding this 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
| def validUnapplySeqType(getTp: Type): Boolean = unapplySeqTypeElemTp(getTp).exists | ||
|
|
||
| if (unapplyName == nme.unapplySeq) { | ||
| if (unapplyResult derivesFrom defn.SeqClass) seqSelector :: Nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why this case removed?
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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) || |
There was a problem hiding this comment.
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
0a8aab6 to
cf40824
Compare
cf40824 to
6a0fc8c
Compare
allanrenucci
left a comment
There was a problem hiding this 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
Fix #4984: support name-based unapplySeq