Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1053,6 +1053,7 @@ class Definitions {
@tu lazy val DeprecatedAnnot: ClassSymbol = requiredClass("scala.deprecated")
@tu lazy val DeprecatedOverridingAnnot: ClassSymbol = requiredClass("scala.deprecatedOverriding")
@tu lazy val DeprecatedInheritanceAnnot: ClassSymbol = requiredClass("scala.deprecatedInheritance")
@tu lazy val ElidableAnnot: ClassSymbol = requiredClass("scala.annotation.elidable")
@tu lazy val ImplicitAmbiguousAnnot: ClassSymbol = requiredClass("scala.annotation.implicitAmbiguous")
@tu lazy val ImplicitNotFoundAnnot: ClassSymbol = requiredClass("scala.annotation.implicitNotFound")
@tu lazy val InferredDepFunAnnot: ClassSymbol = requiredClass("scala.caps.internal.inferredDepFun")
Expand Down
11 changes: 11 additions & 0 deletions compiler/src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1048,6 +1048,17 @@ object SymDenotations {
is(Inline, butNot = Deferred)
&& allOverriddenSymbols.exists(!_.is(Inline))

/** Does this method or field need to be retained at runtime, according to @elidable?
* True by default, to mean yes it can be elided aka erased.
*/
def isElidable(using Context): Boolean =
symbol.getAnnotation(defn.ElidableAnnot) match
case Some(elide) =>
elide.argumentConstant(0) match
case Some(Constant(0)) => false
case _ => true
case _ => true

/** Does this method need to be retained at runtime */
def isRetainedInlineMethod(using Context): Boolean =
is(Method, butNot = Accessor) && isRetainedInline
Expand Down
4 changes: 3 additions & 1 deletion compiler/src/dotty/tools/dotc/transform/Erasure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -888,7 +888,9 @@ object Erasure {
* parameter of type `[]Object`.
*/
override def typedDefDef(ddef: untpd.DefDef, sym: Symbol)(using Context): Tree =
if sym.isEffectivelyErased || sym.name.is(BodyRetainerName) then
if sym.isEffectivelyErased && sym.isElidable
|| sym.name.is(BodyRetainerName)
then
erasedDef(sym)
else
val restpe = if sym.isConstructor then defn.UnitType else sym.info.resultType
Expand Down
8 changes: 6 additions & 2 deletions compiler/src/dotty/tools/dotc/transform/PruneErasedDefs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,15 @@ class PruneErasedDefs extends MiniPhase with SymTransformer:
override def runsAfterGroupsOf: Set[String] = Set(RefChecks.name, ExplicitOuter.name)

override def transformSym(sym: SymDenotation)(using Context): SymDenotation =
if !sym.is(Private) && sym.isEffectivelyErased && sym.isTerm && sym.owner.isClass
if !sym.is(Private)
&& sym.isEffectivelyErased
&& sym.isTerm
&& sym.owner.isClass
&& sym.isElidable
then sym.copySymDenotation(initFlags = sym.flags | Private)
else sym

object PruneErasedDefs:
val name: String = "pruneErasedDefs"
val description: String = "drop erased definitions and simplify erased expressions"
end PruneErasedDefs
end PruneErasedDefs
10 changes: 5 additions & 5 deletions compiler/src/dotty/tools/dotc/typer/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1072,15 +1072,15 @@ trait Implicits:
ltp.isError
|| rtp.isError
|| locally:
if strictEquality then
strictEqualityPatternMatching &&
(leftTree.symbol.isAllOf(Flags.EnumValue) || leftTree.symbol.isAllOf(Flags.Module | Flags.Case)) &&
ltp <:< lift(rtp)
if strictEquality
then strictEqualityPatternMatching
&& (leftTree.symbol.isAllOf(Flags.EnumValue) || leftTree.symbol.isAllOf(Flags.Module | Flags.Case))
&& ltp <:< lift(rtp)
else
ltp <:< lift(rtp) || rtp <:< lift(ltp)
}

/** Check that equality tests between types `ltp` and `left.tpe` make sense.
/** Check that equality tests between types `left.tpe` and `rtp` make sense.
* `left` is required to check for the condition for language.strictEqualityPatternMatching.
*/
def checkCanEqual(left: Tree, rtp: Type, span: Span)(using Context): Unit =
Expand Down
5 changes: 3 additions & 2 deletions library/src/scala/Predef.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import scala.language.`2.13`
import language.experimental.captureChecking
import scala.language.implicitConversions
import scala.collection.{mutable, immutable, ArrayOps, StringOps}, immutable.WrappedString
import scala.annotation.{experimental, implicitNotFound, publicInBinary, targetName, nowarn }
import scala.annotation.{elidable, experimental, implicitNotFound, publicInBinary, targetName, nowarn}
import scala.annotation.meta.{ companionClass, companionMethod }
import scala.annotation.internal.{ RuntimeChecked }
import scala.compiletime.summonFrom
Expand Down Expand Up @@ -272,7 +272,8 @@ object Predef extends LowPriorityImplicits {
* }}}
* @group utilities
*/
@inline def locally[T](@deprecatedName("x") x: T): T = x
@elidable(0)
inline def locally[T](@deprecatedName("x") x: T): T = x

// ==============================================================================================
// ========================================= ASSERTIONS =========================================
Expand Down
95 changes: 7 additions & 88 deletions library/src/scala/annotation/elidable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,73 +17,10 @@ import scala.language.`2.13`
/** An annotation for methods whose bodies may be excluded
* from compiler-generated bytecode.
*
* Behavior is influenced by passing `-Xelide-below <arg>` to `scalac`.
* Calls to methods marked elidable (as well as the method body) will
* be omitted from generated code if the priority given the annotation
* is lower than that given on the command line.
* Annotate an `inline` method with `level` constant `0` (zero)
* to retain the method in byte code.
*
* {{{
* @elidable(123) // annotation priority
* scalac -Xelide-below 456 // command line priority
* }}}
*
* The method call will be replaced with an expression which depends on
* the type of the elided expression. In decreasing order of precedence:
*
* {{{
* Unit ()
* Boolean false
* T <: AnyVal 0
* T >: Null null
* T >: Nothing Predef.???
* }}}
*
* Complete example:
* {{{
* import scala.annotation._, elidable._
* object Test extends App {
* def expensiveComputation(): Int = { Thread.sleep(1000) ; 172 }
*
* @elidable(WARNING) def warning(msg: String) = println(msg)
* @elidable(FINE) def debug(msg: String) = println(msg)
* @elidable(FINE) def computedValue = expensiveComputation()
*
* warning("Warning! Danger! Warning!")
* debug("Debug! Danger! Debug!")
* println("I computed a value: " + computedValue)
* }
* % scalac example.scala && scala Test
* Warning! Danger! Warning!
* Debug! Danger! Debug!
* I computed a value: 172
*
* // INFO lies between WARNING and FINE
* % scalac -Xelide-below INFO example.scala && scala Test
* Warning! Danger! Warning!
* I computed a value: 0
* }}}
*
* Note that only concrete methods can be marked `@elidable`. A non-annotated method
* is not elided, even if it overrides / implements a method that has the annotation.
*
* Also note that the static type determines which annotations are considered:
*
* {{{
* import scala.annotation._, elidable._
* class C { @elidable(0) def f(): Unit = ??? }
* object O extends C { override def f(): Unit = println("O.f") }
* object Test extends App {
* O.f() // not elided
* (O: C).f() // elided if compiled with `-Xelide-below 1`
* }
* }}}
*
* Note for Scala 3 users:
* If you're using Scala 3, the annotation exists since Scala 3 uses the Scala 2
* standard library, but it's unsupported by the Scala 3 compiler. Instead, to
* achieve the same result you'd want to utilize the `inline if` feature to
* introduce behavior that makes a method de facto elided at compile-time.
* {{{
* type LogLevel = Int
*
* object LogLevel:
Expand All @@ -93,37 +30,22 @@ import scala.language.`2.13`
*
* inline val appLogLevel = LogLevel.Warn
*
* @elidable(0) // `log` method is retained in byte code
* inline def log(msg: String, inline level: LogLevel): Unit =
* inline if (level <= appLogLevel) then println(msg)
*
* log("Warn log", LogLevel.Warn)
*
* log("Debug log", LogLevel. Debug)
* }}}
* }}}
*
* Migration note: this annotation had different semantics in Scala 2.
*/
@deprecated(message = "@elidable is not supported by Scala 3", since = "3.8.0")
final class elidable(final val level: Int) extends scala.annotation.ConstantAnnotation

/** This useless appearing code was necessary to allow people to use
* named constants for the elidable annotation. This is what it takes
* to convince the compiler to fold the constants: otherwise when it's
* time to check an elision level it's staring at a tree like
* {{{
* (Select(Level, Select(FINEST, Apply(intValue, Nil))))
* }}}
* instead of the number `300`.
/** Legacy constants, unused in Scala 3.
*/
object elidable {
/** The levels `ALL` and `OFF` are confusing in this context because
* the sentiment being expressed when using the annotation is at cross
* purposes with the one being expressed via `-Xelide-below`. This
* confusion reaches its zenith at level `OFF`, where the annotation means
* ''never elide this method'' but `-Xelide-below OFF` is how you would
* say ''elide everything possible''.
*
* With no simple remedy at hand, the issue is now at least documented,
* and aliases `MAXIMUM` and `MINIMUM` are offered.
*/
final val ALL = Int.MinValue // Level.ALL.intValue()
final val FINEST = 300 // Level.FINEST.intValue()
final val FINER = 400 // Level.FINER.intValue()
Expand All @@ -134,14 +56,11 @@ object elidable {
final val SEVERE = 1000 // Level.SEVERE.intValue()
final val OFF = Int.MaxValue // Level.OFF.intValue()

// a couple aliases for the confusing ALL and OFF
final val MAXIMUM = OFF
final val MINIMUM = ALL

// and we can add a few of our own
final val ASSERTION = 2000 // we should make this more granular

// for command line parsing so we can use names or ints
val byName: Map[String, Int] = Map(
"FINEST" -> FINEST,
"FINER" -> FINER,
Expand Down
2 changes: 1 addition & 1 deletion tests/neg-macros/annot-crash.check
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
|^^^^^^
|Failed to evaluate macro annotation '@crash'.
| Caused by class scala.NotImplementedError: an implementation is missing
| scala.Predef$.$qmark$qmark$qmark(Predef.scala:387)
| crash.$qmark$qmark$qmark(Macro_1.scala:9)
| crash.transform(Macro_1.scala:7)
2 changes: 2 additions & 0 deletions tests/neg-macros/annot-crash/Macro_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ import scala.quoted._
class crash extends MacroAnnotation {
def transform(using Quotes)(definition: quotes.reflect.Definition, companion: Option[quotes.reflect.Definition]): List[quotes.reflect.Definition] =
???

def ??? : Nothing = throw new NotImplementedError
}
26 changes: 26 additions & 0 deletions tests/run/getclass-21.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
Value types:
void
boolean
byte
short
char
int
long
float
double

Class types:
class SomeClass
class ValueClass
class scala.collection.immutable.$colon$colon
class scala.Tuple2

Arrays:
class [Lscala.runtime.BoxedUnit;
class [I
class [D
class [Lscala.collection.immutable.List;

Functions:
class Test$$$Lambda/
class Test$$$Lambda/
45 changes: 45 additions & 0 deletions tests/run/getclass-21.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// scalajs: --skip
// test: -jvm 21+

class ValueClass(val i: Integer) extends AnyVal
class SomeClass

object Test {
def main(args: Array[String]): Unit = {
val cls: Predef.Class[_] = new SomeClass().getClass
val valCls: Predef.Class[_] = new ValueClass(1).getClass
val iCls: Class[Int] = 1.getClass
val f1: Function2[Int, Int, Unit] = (a: Int, b: Int) => println(a + b)
val f2: Function1[Int, Boolean] = (a: Int) => a % 2 == 0
val one = 1

println("Value types:")
println(().getClass)
println(true.getClass)
println(1.asInstanceOf[Byte].getClass)
println(1.asInstanceOf[Short].getClass)
println('a'.getClass)
println(one.getClass)
println(1L.getClass)
println(1f.getClass)
println(1d.getClass)

println("\nClass types:")
println(new SomeClass().getClass)
println(new ValueClass(1).getClass)
println(List(Array(1f)).getClass)
println(("a", Map(1 -> "b")).getClass)

println("\nArrays:")
println(Array(()).getClass)
println(Array(1).getClass)
println(Array(1d).getClass)
println(Array(List("1")).getClass)

println("\nFunctions:")
// FunctionN.getClass.toString has form of "class Test$$$Lambda$N/1349414238",
// but "N/1349414238" depends on environment
println(f1.getClass.toString.take("class Test$$$Lambda$".length))
println(f2.getClass.toString.take("class Test$$$Lambda$".length))
}
}
1 change: 1 addition & 0 deletions tests/run/getclass.scala
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// scalajs: --skip
// test: -jvm 17

class ValueClass(val i: Integer) extends AnyVal
class SomeClass
Expand Down
11 changes: 11 additions & 0 deletions tests/run/i24553-21.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
public boolean java.lang.Object.equals(java.lang.Object)
public final native java.lang.Class<?> java.lang.Object.getClass()
public native int java.lang.Object.hashCode()
public int Foo.hello()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
public java.lang.String java.lang.Object.toString()
public final void java.lang.Object.wait(long) throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public int Foo.x()
9 changes: 9 additions & 0 deletions tests/run/i24553-21.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// scalajs: --skip
// test: -jvm 21+
class Foo:
val hello = 1337
val x: hello.type = ???

@main def Test =
val mtds = classOf[Foo].getMethods().sortBy(_.getName())
for mtd <- mtds do println(mtd.toGenericString())
1 change: 1 addition & 0 deletions tests/run/i24553.scala
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// scalajs: --skip
// test: -jvm 17
class Foo:
val hello = 1337
val x: hello.type = ???
Expand Down
Loading