-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Fix #4177: Generate optimised applyOrElse implementation for partial function literals #4245
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
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.
Hello, and thank you for opening this PR! 🎉
All contributors have signed the CLA, thank you! ❤️
Commit Messages
We want to keep history, but for that to actually be useful we have
some rules on how to format our commit messages (relevant xkcd).
Please stick to these guidelines for commit messages:
- Separate subject from body with a blank line
- When fixing an issue, start your commit message with
Fix #<ISSUE-NBR>:
- Limit the subject line to 72 characters
- Capitalize the subject line
- Do not end the subject line with a period
- Use the imperative mood in the subject line ("Add" instead of "Added")
- Wrap the body at 80 characters
- Use the body to explain what and why vs. how
adapted from https://chris.beams.io/posts/git-commit
Have an awesome day! ☀️
@@ -585,6 +585,10 @@ class Definitions { | |||
|
|||
lazy val PartialFunctionType: TypeRef = ctx.requiredClassRef("scala.PartialFunction") | |||
def PartialFunctionClass(implicit ctx: Context) = PartialFunctionType.symbol.asClass | |||
|
|||
lazy val PartialFunction_applyOrElseR = PartialFunctionClass.requiredMethodRef(nme.applyOrElse) |
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 has the wrong indent size?
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 is the indentation scheme used across the 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.
Members of classes are indented relative to their parent.
val applyFn = applyDef.symbol.asTerm | ||
|
||
val MethodTpe(paramNames, paramTypes, _) = applyFn.info | ||
val MethodTpe(paramNames, paramTypes, _) = applyFn.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.
indent issue here too?
@@ -274,7 +274,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { | |||
val constr = ctx.newConstructor(cls, Synthetic, Nil, Nil).entered | |||
def forwarder(fn: TermSymbol, name: TermName) = { | |||
val fwdMeth = fn.copy(cls, name, Synthetic | Method).entered.asTerm |
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.
Adding the Override
modifier here when needed should fix the test failures. In this PR, we override PartialFunction::applyOrElse
. I was not able to reproduce the compiler errors with a minimised test case, only by compiling Dotty itself
180cd13
to
641f84e
Compare
val fwdMeth = fn.copy(cls, name, Synthetic | Method).entered.asTerm | ||
DefDef(fwdMeth, prefss => ref(fn).appliedToArgss(prefss)) | ||
var flags = Synthetic | Method | ||
val isOverride = parents.exists(_.member(name).hasAltWith(_.info == fn.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.
Is this the proper way to check if the newly defined symbol overrides something in the parents?
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.
See SymDenotation#allOverriddenSymbols
cpy.Block(tree)(List(applyDef, isDefinedAtDef), anonCls) | ||
val applyOrElseDef = transformFollowingDeep(DefDef(applyOrElseFn, applyOrElseRhs(_))) | ||
|
||
val anonCls = AnonClass(tpt.tpe :: Nil, List(applyFn, isDefinedAtFn, applyOrElseFn), List(nme.apply, nme.isDefinedAt, nme.applyOrElse)) |
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 followed the previous scheme. But, is there a reason to use forwarder and not inline apply
, isDefinedAtFn
and applyOrElseFn
in the anonymous class?
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 don't think there's a reason. We could implement them directly.
test performance please |
performance test scheduled: 1 job(s) in queue, 1 running. |
performance test failed: Please check http://lamppc37.epfl.ch:8000/pull-4245-04-11-16.21.out for more information |
@allanrenucci can you try rebase? It seems this PR has some interaction with |
@liufengyun I was able to minimise the issue. The following code snippet reports an unchecked warning: class Test {
def test[X <: Option[Int]](x: X) = x.isInstanceOf[Some[Int]]
} |
Thanks a lot @allanrenucci , I'm investigating it, type inference somehow failed to infer |
Blocked by #4297 |
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
only emits two methods for partial function: isDefined
and applyOrElse
, or if the match is total, apply
. This works because it inherits from AbstractPartialFunction
which implements apply
in terms of applyOrElse
and applyOrElse
in terms of apply
. We should do the same thing. It's potentially expensive to duplicate a large pattern match.
cpy.Block(tree)(List(applyDef, isDefinedAtDef), anonCls) | ||
val applyOrElseDef = transformFollowingDeep(DefDef(applyOrElseFn, applyOrElseRhs(_))) | ||
|
||
val anonCls = AnonClass(tpt.tpe :: Nil, List(applyFn, isDefinedAtFn, applyOrElseFn), List(nme.apply, nme.isDefinedAt, nme.applyOrElse)) |
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 don't think there's a reason. We could implement them directly.
@odersky I am not sure to understand what you mean here. If I compile val x: PartialFunction[Int,Int] = ({
class $anonfun extends AbstractPartialFunction[Int,Int] {
override def applyOrElse[A1 <: Int, B1 >: Int](x1: A1, default: A1 => B1): B1 = (x1: @unchecked) match {
case (x @ _) => x
case (defaultCase$ @ _) => default.apply(x1)
}
def isDefinedAt(x1: Int): Boolean = (x1: @unchecked) match {
case (x @ _) => true
case (defaultCase$ @ _) => false
}
}
new $anonfun()
}: PartialFunction[Int,Int])
Did you mean that in the second case we can do better than val x: PartialFunction[Int,Int] = ({
class $anonfun extends PartialFunction[Int,Int] {
def apply(x: Int) = x
def isDefinedAt(x1: Int): Boolean = true
}
}
new $anonfun()
}: PartialFunction[Int,Int]) To know if the match is total, we can use a simple heuristic: if the RHS of the anonymous function is a |
8cb059e
to
4da33ae
Compare
…tial function literals
…rals This is done by extending `scala.runtime.AbstractPartialFunction`.
- `x => x match { case x => x }` is a PF - `x => { x match { case x => x } }` is a PF - `x => { println("foo"); x match { case x => x } }` is not a PF - `x => x` is not a PF
E.g. ``` class Foo(val field: Option[Int]) val p: PartialFunction[Foo, Int] = foo => foo.field match { case Some(x) => x } ```
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 appreciated the crystal-clear code! LGTM.
Fix #4177.