diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index f3e395b505f3..dee922f28b21 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -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") diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index f8b8964ac26e..42c7c0055339 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -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 diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index 99cb74ecac7f..903d11fbfb05 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -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 diff --git a/compiler/src/dotty/tools/dotc/transform/PruneErasedDefs.scala b/compiler/src/dotty/tools/dotc/transform/PruneErasedDefs.scala index a72c0f9bed7c..7630209cbc56 100644 --- a/compiler/src/dotty/tools/dotc/transform/PruneErasedDefs.scala +++ b/compiler/src/dotty/tools/dotc/transform/PruneErasedDefs.scala @@ -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 \ No newline at end of file +end PruneErasedDefs diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 342cc8508d54..dcca3ea887ce 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -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 = diff --git a/library/src/scala/Predef.scala b/library/src/scala/Predef.scala index 2424265ba05a..4dfbe7cf3027 100644 --- a/library/src/scala/Predef.scala +++ b/library/src/scala/Predef.scala @@ -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 @@ -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 ========================================= diff --git a/library/src/scala/annotation/elidable.scala b/library/src/scala/annotation/elidable.scala index cf79e03cfdf9..3a68d9793a9f 100644 --- a/library/src/scala/annotation/elidable.scala +++ b/library/src/scala/annotation/elidable.scala @@ -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 ` 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: @@ -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() @@ -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, diff --git a/tests/neg-macros/annot-crash.check b/tests/neg-macros/annot-crash.check index ae0f64f12847..280e189f896c 100644 --- a/tests/neg-macros/annot-crash.check +++ b/tests/neg-macros/annot-crash.check @@ -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) diff --git a/tests/neg-macros/annot-crash/Macro_1.scala b/tests/neg-macros/annot-crash/Macro_1.scala index 06fb08062181..c3268d94b420 100644 --- a/tests/neg-macros/annot-crash/Macro_1.scala +++ b/tests/neg-macros/annot-crash/Macro_1.scala @@ -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 } diff --git a/tests/run/getclass-21.check b/tests/run/getclass-21.check new file mode 100644 index 000000000000..f5f412ab2edc --- /dev/null +++ b/tests/run/getclass-21.check @@ -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/ diff --git a/tests/run/getclass-21.scala b/tests/run/getclass-21.scala new file mode 100644 index 000000000000..802e064a6279 --- /dev/null +++ b/tests/run/getclass-21.scala @@ -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)) + } +} diff --git a/tests/run/getclass.scala b/tests/run/getclass.scala index d8a339f8ea5a..3b170f7c409f 100644 --- a/tests/run/getclass.scala +++ b/tests/run/getclass.scala @@ -1,4 +1,5 @@ // scalajs: --skip +// test: -jvm 17 class ValueClass(val i: Integer) extends AnyVal class SomeClass diff --git a/tests/run/i24553-21.check b/tests/run/i24553-21.check new file mode 100644 index 000000000000..9380e2cc4701 --- /dev/null +++ b/tests/run/i24553-21.check @@ -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() diff --git a/tests/run/i24553-21.scala b/tests/run/i24553-21.scala new file mode 100644 index 000000000000..95ed4dd3359b --- /dev/null +++ b/tests/run/i24553-21.scala @@ -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()) diff --git a/tests/run/i24553.scala b/tests/run/i24553.scala index 910c287c5c7f..39ca02a46560 100644 --- a/tests/run/i24553.scala +++ b/tests/run/i24553.scala @@ -1,4 +1,5 @@ // scalajs: --skip +// test: -jvm 17 class Foo: val hello = 1337 val x: hello.type = ???