Skip to content

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

Merged
merged 5 commits into from
Apr 27, 2018

Conversation

allanrenucci
Copy link
Contributor

@allanrenucci allanrenucci commented Apr 4, 2018

Fix #4177.

Copy link
Member

@dottybot dottybot left a 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:

  1. Separate subject from body with a blank line
  2. When fixing an issue, start your commit message with Fix #<ISSUE-NBR>:
  3. Limit the subject line to 72 characters
  4. Capitalize the subject line
  5. Do not end the subject line with a period
  6. Use the imperative mood in the subject line ("Add" instead of "Added")
  7. Wrap the body at 80 characters
  8. 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)
Copy link
Contributor

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?

Copy link
Contributor Author

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

Copy link
Contributor

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

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

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

@allanrenucci allanrenucci force-pushed the fix-4177 branch 2 times, most recently from 180cd13 to 641f84e Compare April 5, 2018 10:10
@allanrenucci allanrenucci changed the title [WIP] Fix #4177: Generate optimised applyOrElse implementation for partial function literals Fix #4177: Generate optimised applyOrElse implementation for partial function literals Apr 5, 2018
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))
Copy link
Contributor Author

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?

Copy link
Member

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

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?

Copy link
Contributor

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.

@allanrenucci
Copy link
Contributor Author

test performance please

@dottybot
Copy link
Member

performance test scheduled: 1 job(s) in queue, 1 running.

@dottybot
Copy link
Member

performance test failed:

Please check http://lamppc37.epfl.ch:8000/pull-4245-04-11-16.21.out for more information

@scala scala deleted a comment from dottybot Apr 11, 2018
@scala scala deleted a comment from dottybot Apr 11, 2018
@scala scala deleted a comment from dottybot Apr 11, 2018
@scala scala deleted a comment from dottybot Apr 11, 2018
@scala scala deleted a comment from dottybot Apr 11, 2018
@liufengyun
Copy link
Contributor

@allanrenucci can you try rebase? It seems this PR has some interaction with isInstanceofCheck in the case of partial functions.

@allanrenucci
Copy link
Contributor Author

@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]]
}

@liufengyun
Copy link
Contributor

liufengyun commented Apr 11, 2018

Thanks a lot @allanrenucci , I'm investigating it, type inference somehow failed to infer a <: Int, given Some[a] <: X and X <: Option[Int].

@allanrenucci
Copy link
Contributor Author

Blocked by #4297

Copy link
Contributor

@odersky odersky left a 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))
Copy link
Contributor

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 odersky assigned allanrenucci and unassigned odersky Apr 16, 2018
@allanrenucci
Copy link
Contributor Author

allanrenucci commented Apr 17, 2018

or if the match is total, apply

@odersky I am not sure to understand what you mean here.

If I compile val x: PartialFunction[Int, Int] = { case x => x } with scalac I get something like:

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

val x: PartialFunction[Int, Int] = (x: Int) => x doesn't even compile with scalac.

Did you mean that in the second case we can do better than scalac. We know that the match is total an can generate something like:

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 Match then it is not total otherwise it is

@Blaisorblade
Copy link
Contributor

if the RHS of the anonymous function is a Match then it is not total otherwise it is

I assume you include x => { x match { case 1 => x.toString } } as a PartialFunction, per #4241.


(Edited description so the PR is linked in #4177, "Fix #NNNN" in the title doesn't work).

@allanrenucci allanrenucci force-pushed the fix-4177 branch 3 times, most recently from 8cb059e to 4da33ae Compare April 20, 2018 17:25
…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 }
```
Copy link
Contributor

@odersky odersky left a 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.

@odersky odersky merged commit 770320b into scala:master Apr 27, 2018
@allanrenucci allanrenucci deleted the fix-4177 branch April 27, 2018 15:35
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.

6 participants