8000 SI-10068 Only permit elidable methods by som-snytt · Pull Request #5539 · scala/scala · GitHub
[go: up one dir, main page]

Skip to content

SI-10068 Only permit elidable methods #5539

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 1 commit into from
Dec 19, 2016
Merged

Conversation

som-snytt
Copy link
Contributor
@som-snytt som-snytt commented Nov 17, 2016

In uncurry, check that symbol with @elidable is a method.

This fails to notice the annotation on lazy vals, which seems
to be a product of synthesis at namer/typer.

JIRA: https://issues.scala-lang.org/browse/SI-10068

@som-snytt
Copy link
Contributor Author
som-snytt commented Nov 18, 2016

Somebody clever:

// annotations on abstract types
abstract class C1[@annotation.elidable(0) +T, U, V[_]]

pos/annotations.scala
pos/spec-annotations.scala

Edit: it used to read @cloneable.

@Atry
Copy link
Contributor
Atry commented Nov 18, 2016

This PR is a regression, as the current compiler allows @elidable(INFO) val i: Int = 42 .

@som-snytt
Copy link
Contributor Author

@Atry I think that was just a bug.

The doc says:

An annotation for methods whose bodies may be excluded from compiler-generated bytecode.

It's kind of too bad Scala doesn't have the crazy Java ways to control where an annotation will stick.

It worked by accident for vals by converting the RHS of the accessor method and replacing the field definition itself with an anomalous null. (Everything that isn't a method becomes null.asInstanceOf[A].)

My first thought was to make it work for variables (see comment in test), but it doesn't work for constant value defs and lazy vals. It couldn't work for constants, though probably could be made to work for lazy vals. The shortcoming in this PR is that it still just silently ignores lazy vals.

$ scala -Xelide-below WARNING
Welcome to Scala 2.12.0 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_111).
Type in expressions for evaluation. Or try :help.

scala> import annotation._, elidable._
import annotation._
import elidable._

scala> @elidable(INFO) final val x = 42
x: Int(42) = 42

scala> def f = x
f: Int

scala> f
res0: Int = 42

scala> @elidable(INFO) def g() = 42
g: ()Int

scala> def f = g
f: Int

scala> f
res1: Int = 0

scala> @elidable(INFO) lazy val x = 42
x: Int = <lazy>

scala> def f = x
f: Int

scala> f
res2: Int = 42

scala> 

@dragos
Copy link
Contributor
dragos commented Nov 25, 2016

I understand that the intent of this annotation is indeed to be for methods. I am not sure if there is code out there relying on this, but it's source-incompatible. Should this target 2.13 then?

Regardless of that, I don't think uncurry is the right place to emit these errors. Doing this in typer may actually allow you to catch lazy vals as well.

@som-snytt
Copy link
Contributor Author
som-snytt commented Nov 25, 2016

@dragos I tried what I thought was the obvious spot in typer (typedValDefImpl) and missed, so I did what worked.

I don't have a strong opinion about 2.13, except that it's just a bug and not a de facto standard. I'd be generous if it weren't documented, or if the community build breaks.

@dragos
Copy link
Contributor
dragos commented Nov 25, 2016

I don't feel strongly about 2.13, as you are right that it's forbidding a corner-case in a fairly esoteric option.

However, I feel a bit stronger about where this check should be done. Besides the design intent of Typer and Uncurry, the latter is not run by the presentation compiler, so Ensime/Eclipse users won't be seeing these errors before building.

If you are stuck, I'm sure there are people on scala-internals who could help.

@som-snytt
Copy link
Contributor Author

@dragos How about refchecks?

There some annotation checks in Typers but it doesn't try too hard:

scala> class C[@specialized A](a: A)
defined class C

scala> @specialized class D[A](a: A)
defined class D

scala> class E[@specialized A](val a: A) extends AnyVal
<console>:11: error: type parameter of value class may not be specialized
       class E[@specialized A](val a: A) extends AnyVal
                            ^

scala> import annotation._, elidable._
import annotation._
import elidable._

scala> @elidable(INFO) def f = 42
f: Int

scala> @elidable(INFO) class F
<console>:17: error: F: Only methods can be marked @elidable.
       @elidable(INFO) class F
                             ^

@dragos
Copy link
Contributor
dragos commented Nov 28, 2016

Much better! If you push a version in RefCheck I could have a look and see if it's easy to move one notch to the left, in Typer. :)

@lrytz
Copy link
Member
lrytz commented Dec 12, 2016

Agree for preferring Typers (or RefChecks). You could put in 2.12 under -Xsource:2.13 as a compromise :)

@som-snytt som-snytt force-pushed the issue/10068 branch 3 times, most recently from 0d9cc0d to 737427d Compare December 13, 2016 08:35
@som-snytt
Copy link
Contributor Author
[warn] Unable to reparse org.scala-lang#scala-library;2.12.2-737427d-SNAPSHOT from private-repo, using Tue Dec 13 08:43:46 UTC 2016
[warn] Unable to reparse org.scala-lang#scala-compiler;2.12.2-737427d-SNAPSHOT from private-repo, using Tue Dec 13 08:54:28 UTC 2016
[warn] Unable to reparse org.scala-lang#scala-reflect;2.12.2-737427d-SNAPSHOT from private-repo, using Tue Dec 13 08:46:40 UTC 2016
[info] Compiling 582 Scala sources and 120 Java sources to /home/jenkins/workspace/scala-2.12.x-validate-test@2/build/quick/classes/library...
[info] 'compiler-interface' not yet compiled for Scala 2.12.2-20161213-003525-737427d. Compiling...
error: scala.MatchError: null (of class scala.reflect.internal.Trees$Literal)
	at scala.tools.nsc.backend.jvm.GenBCode$BCodePhase.gen$1(GenBCode.scala:449)
	at scala.tools.nsc.backend.jvm.GenBCode$BCodePhase.apply(GenBCode.scala:453)

@som-snytt
Copy link
Contributor Author

Passes modulo mima.

@lrytz
Copy link
Member
lrytz commented Dec 14, 2016

Looks good, thanks! The MiMa failure will go away after a rebase. @dragos, do you think the check should move to Typers?

In refchecks, check that symbol with `@elidable` is a method.

When eliding in uncurry, doublecheck.

The check is enabled under `-Xsource:2.13`.
@adriaanm
Copy link
Contributor

LGTM -- excellent! Also much pleased to see we're allocating fewer ScalaVersion objects :-)

@adriaanm adriaanm merged commit 56fb917 into scala:2.12.x Dec 19, 2016
@@ -84,7 +84,10 @@ trait ScalaSettings extends AbsScalaSettings
* though this helper.
*/
def isScala211: Boolean = source.value >= ScalaVersion("2.11.0")
def isScala212: Boolean = source.value >= ScalaVersion("2.12.0")
private[this] val version212 = ScalaVersion("2.12.0")
Copy link
Member

Choose a reason for hiding this comment

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

Forgot ScalaVersion("2.11.0") ?

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 didn't want to push it.

@lrytz
Copy link
Member
lrytz commented Sep 22, 2017

@som-snytt I stumbled over this PR while enabling Xsource:2.13 by default in the 2.13 branch. I wonder why there's code to report an error both in RefChecks and UnCurry. Are there cases that slip the first check?

Also, the error is reported if the annotation is on an abstract method. This makes sense, as the annotation is not "inherited", we don't check if an overridden member has the annotation. I assume this is intentional? I'll update the error message, because it can be confusing:

Test.scala:5: error: f1: Only methods can be marked @elidable.
  @elidable(MINIMUM) def f1(): Unit
                         ^

@lrytz
Copy link
Member
lrytz commented Sep 22, 2017

Ah, the static type is relevant:

import annotation._
import elidable._

abstract class A {
  @elidable(10) def f(): Unit
}

class C extends A {
  def f(): Unit = println("f")
}

object Test {
  def main(args: Array[String]): Unit = {
    val c = new C
    c.f()
    (c: A).f()
  }
}

In 2.12, the above prints f once. With -Xsource:2.13 there's a compiler error. Is this a reason to allow the annotation on abstract methods?

@som-snytt
Copy link
Contributor Author

@lrytz I don't remember directly, but from the comments it looks like I had trouble detecting a lazy val when I was trying to support elidable lazy? I don't know what paranoia made me leave the later check. The message could have been augmented under debug, and then tests could document where the error was issued. I should have written more tests. Your clever example rings a bell, but it seems to me a reason not to allow it. Now we need a new lint rule to warn about overriding an elidable with a non-elidable. Which is the officially blessed lint project now?

In the inverse case, the call to A.f is not elided, but the RHS of C.f is. OK, so maybe it ought to be allowed. The story for overriding is very fragile.

Not supporting vals is too bad. An elidable debug flag is an obvious use case. (It's a value with a useful natural default.)

@lrytz
Copy link
Member
lrytz commented Sep 23, 2017

Lazy vals are caught by the first test, so probably it's just an oversight. Let's remove the report in UnCurry.

About @elidable on an abstract method, I think it makes sense to leave it as-is (report as error). I don't think anyone should rely on the receiver type / having a different annotation on an overriden method.

Which is the officially blessed lint project now?

Time, adoption, contributors will tell :-) You were the primary contributor to -Xlint in the recent past, so you do has an impact. Scalafix is young, it has sbt integration but needs to be called explicitly, and no IDE integration..

@som-snytt
Copy link
Contributor Author

A use case would be a logger interface with implementations. My app code could then elide logger.log() entirely. Oh, in fact under separate compilation, my logger library needs the elidable abstract, since loggerImpl.log() won't be recompiled.

Yeah, -Xlint was a nice hole I dug.

@lrytz
Copy link
Member
lrytz commented Sep 24, 2017

I'm happy to have it changed back to allow it on abstract members. Here's my clarifications for the status quo: d5bf1d7, part of #6092 for 2.13.x

@som-snytt
Copy link
Contributor Author

@lrytz thanks for the info. I'd wait for someone to ask for the feature, as there is no right path. I was only speaking hypothetically about a logger interface.

My pet use case for elidable was for API prioritization, where methods marked "must have" would be enabled earlier in a development cycle. The elide level in the build would gradually fall until all features goals are met. That's also a hypothetical workflow.

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.

8 participants
0