-
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
@@ -100,10 +100,27 @@ object Applications { | |||
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 |
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
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
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