Content-Length: 776012 | pFad | http://github.com/scala/scala/pull/10959/files

20 @nullOut annotation by lrytz · Pull Request #10959 · scala/scala · GitHub
Skip to content

@nullOut annotation #10959

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

Draft
wants to merge 2 commits into
base: 2.13.x
Choose a base branch
from
Draft
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
3 changes: 3 additions & 0 deletions project/MimaFilters.scala
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ object MimaFilters extends AutoPlugin {
ProblemFilters.exclude[IncompatibleResultTypeProblem]("scala.collection.immutable.LazyList#LazyBuilder#DeferredState.eval"),
ProblemFilters.exclude[MissingClassProblem]("scala.collection.immutable.LazyList$MidEvaluation$"),
ProblemFilters.exclude[MissingClassProblem]("scala.collection.immutable.LazyList$Uninitialized$"),

// scala/scala#10959
ProblemFilters.exclude[MissingClassProblem]("scala.annotation.nullOut"),
)

override val buildSettings = Seq(
Expand Down
12 changes: 11 additions & 1 deletion src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,10 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
}
else {
mnode.visitVarInsn(asm.Opcodes.ALOAD, 0)
if(tree.hasAttachment[NullOutAttachment.type]) {
mnode.visitInsn(asm.Opcodes.ACONST_NULL)
mnode.visitVarInsn(asm.Opcodes.ASTORE, 0)
}
// When compiling Array.scala, the constructor invokes `Array.this.super.<init>`. The expectedType
// is `[Object` (computed by typeToBType, the type of This(Array) is `Array[T]`). If we would set
// the generatedType to `Array` below, the call to adapt at the end would fail. The situation is
Expand Down Expand Up @@ -400,7 +404,13 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
if (!sym.hasPackageFlag) {
val tk = symInfoTK(sym)
if (sym.isModule) { genLoadModule(tree) }
else { locals.load(sym) }
else {
locals.load(sym)
if(tree.hasAttachment[NullOutAttachment.type]) {
mnode.visitInsn(asm.Opcodes.ACONST_NULL)
locals.store(sym)
}
}
generatedType = tk
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -549,9 +549,10 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
val dd1 = global.gen.mkStatic(deriveDefDef(dd)(_ => EmptyTree), newTermName(traitSuperAccessorName(sym)), _.cloneSymbol.withoutAnnotations)
dd1.symbol.setFlag(Flags.ARTIFACT).resetFlag(Flags.OVERRIDE)
val selfParam :: realParams = dd1.vparamss.head.map(_.symbol): @unchecked
import scala.util.chaining._
deriveDefDef(dd1)(_ =>
atPos(dd1.pos)(
Apply(Select(global.gen.mkAttributedIdent(selfParam).setType(sym.owner.typeConstructor), dd.symbol),
Apply(Select(global.gen.mkAttributedIdent(selfParam).setType(sym.owner.typeConstructor).tap(s => if (sym.hasAnnotation(definitions.NullOutClass)) s.updateAttachment(NullOutAttachment)), dd.symbol),
realParams.map(global.gen.mkAttributedIdent)).updateAttachment(UseInvokeSpecial))
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,6 @@ abstract class CopyProp {
val canElim = vi.getOpcode != ASTORE || {
val currentFieldValueProds = prodCons.initialProducersForValueAt(vi, vi.`var`)
currentFieldValueProds.size == 1 && (currentFieldValueProds.head match {
case ParameterProducer(0) => !isStaticMethod(method) // current field value is `this`, which won't be gc'd anyway
case _: UninitializedLocalProducer => true // field is not yet initialized, so current value cannot leak
case _ => false
})
Expand Down
7 changes: 6 additions & 1 deletion src/compiler/scala/tools/nsc/transform/Erasure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import scala.collection.{immutable, mutable}
import symtab._
import Flags._
import scala.reflect.internal.Mode._
import scala.util.chaining._

abstract class Erasure extends InfoTransform
with scala.reflect.internal.transform.Erasure
Expand Down Expand Up @@ -660,7 +661,7 @@ abstract class Erasure extends InfoTransform
val rhs = member.tpe match {
case MethodType(Nil, FoldableConstantType(c)) => Literal(c)
case _ =>
val sel: Tree = gen.mkAttributedSelect(gen.mkAttributedThis(root), member)
val sel: Tree = gen.mkAttributedSelect(gen.mkAttributedThis(root).tap(t => if (member.hasAnnotation(definitions.NullOutClass)) t.updateAttachment(NullOutAttachment)), member)
val bridgingCall = bridge.paramss.foldLeft(sel)((fun, vparams) => Apply(fun, vparams map Ident))

maybeWrap(bridgingCall)
Expand Down Expand Up @@ -1331,6 +1332,10 @@ abstract class Erasure extends InfoTransform
}
fun

case tree @ Typed(expr, tt @ TypeTree()) if tt.tpe.hasAnnotation(definitions.NullOutClass) =>
expr.updateAttachment(NullOutAttachment)
tree

case _ =>
tree
}
Expand Down
7 changes: 6 additions & 1 deletion src/compiler/scala/tools/nsc/transform/Mixin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import scala.annotation.tailrec
import scala.collection.mutable
import scala.reflect.NameTransformer
import scala.reflect.internal.util.ListOfNil
import scala.util.chaining._


abstract class Mixin extends Transform with ast.TreeDSL with AccessorSynthesis {
Expand Down Expand Up @@ -564,7 +565,11 @@ abstract class Mixin extends Transform with ast.TreeDSL with AccessorSynthesis {
else if (!sym.isMacro) { // forwarder
assert(sym.alias != NoSymbol, (sym, sym.debugFlagString, clazz))
// debuglog("New forwarder: " + sym.defString + " => " + sym.alias.defString)
addDefDef(sym, Apply(SuperSelect(clazz, sym.alias), sym.paramss.head.map(Ident(_))))
addDefDef(sym, Apply(SuperSelect(clazz, sym.alias).tap({
case Select(Super(t: This, _), _) if sym.alias.hasAnnotation(definitions.NullOutClass) =>
t.updateAttachment(NullOutAttachment)
case _ =>
}), sym.paramss.head.map(Ident(_))))
}
}

Expand Down
15 changes: 15 additions & 0 deletions src/library/scala/annotation/nullOut.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* Scala (https://www.scala-lang.org)
*
* Copyright EPFL and Lightbend, Inc. dba Akka
*
* Licensed under Apache License 2.0
* (http://www.apache.org/licenses/LICENSE-2.0).
*
* See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*/

package scala.annotation

final class nullOut extends scala.annotation.StaticAnnotation
17 changes: 11 additions & 6 deletions src/library/scala/collection/IterableOnce.scala
Original file line number Diff line number Diff line change
Expand Up @@ -722,7 +722,7 @@ trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOnce[A] =>
case seq: IndexedSeq[A @unchecked] => foldl[A, B](seq, 0, z, op)
case _ =>
var result = z
val it = iterator
val it = (this: @annotation.nullOut).iterator
while (it.hasNext) {
result = op(result, it.next())
}
Expand Down Expand Up @@ -755,7 +755,8 @@ trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOnce[A] =>
def foldRight[B](z: B)(op: (A, B) => B): B = reversed.foldLeft(z)((b, a) => op(a, b))

@deprecated("Use foldLeft instead of /:", "2.13.0")
@`inline` final def /: [B](z: B)(op: (B, A) => B): B = foldLeft[B](z)(op)
@annotation.nullOut
@`inline` final def /: [B](z: B)(op: (B, A) => B): B = (this: @annotation.nullOut).foldLeft[B](z)(op)

@deprecated("Use foldRight instead of :\\", "2.13.0")
@`inline` final def :\ [B](z: B)(op: (A, B) => B): B = foldRight[B](z)(op)
Expand Down Expand Up @@ -1313,9 +1314,10 @@ trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOnce[A] =>
*
* @example `List(1, 2, 3).mkString("(", "; ", ")") = "(1; 2; 3)"`
*/
@annotation.nullOut
final def mkString(start: String, sep: String, end: String): String =
if (knownSize == 0) start + end
else addString(new StringBuilder(), start, sep, end).result()
else (this: @annotation.nullOut).addString(new StringBuilder(), start, sep, end).result()

/** Displays all elements of this $coll in a string using a separator string.
*
Expand All @@ -1328,7 +1330,8 @@ trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOnce[A] =>
*
* @example `List(1, 2, 3).mkString("|") = "1|2|3"`
*/
@inline final def mkString(sep: String): String = mkString("", sep, "")
@annotation.nullOut
@inline final def mkString(sep: String): String = (this: @annotation.nullOut).mkString("", sep, "")

/** Displays all elements of this $coll in a string.
*
Expand All @@ -1339,7 +1342,8 @@ trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOnce[A] =>
* of all elements of this $coll follow each other without any
* separator string.
*/
@inline final def mkString: String = mkString("")
@annotation.nullOut
@inline final def mkString: String = (this: @annotation.nullOut).mkString("")

/** Appends all elements of this $coll to a string builder using start, end, and separator strings.
* The written text begins with the string `start` and ends with the string `end`.
Expand All @@ -1365,10 +1369,11 @@ trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOnce[A] =>
* @param end the ending string.
* @return the string builder `b` to which elements were appended.
*/
@annotation.nullOut
def addString(b: StringBuilder, start: String, sep: String, end: String): b.type = {
val jsb = b.underlying
if (start.length != 0) jsb.append(start)
val it = iterator
val it = (this: @annotation.nullOut).iterator
if (it.hasNext) {
jsb.append(it.next())
while (it.hasNext) {
Expand Down
3 changes: 2 additions & 1 deletion src/library/scala/collection/LinearSeq.scala
Original file line number Diff line number Diff line change
Expand Up @@ -176,9 +176,10 @@ trait LinearSeqOps[+A, +CC[X] <: LinearSeq[X], +C <: LinearSeq[A] with LinearSeq
None
}

@annotation.nullOut
override def foldLeft[B](z: B)(op: (B, A) => B): B = {
var acc = z
var these: LinearSeq[A] = coll
var these: LinearSeq[A] = (this: @annotation.nullOut).coll
while (!these.isEmpty) {
acc = op(acc, these.head)
these = these.tail
Expand Down
13 changes: 0 additions & 13 deletions src/library/scala/collection/immutable/LazyList.scala
Original file line number Diff line number Diff line change
Expand Up @@ -416,19 +416,6 @@ final class LazyList[+A] private (lazyState: AnyRef /* EmptyMarker.type | () =>
}
}

/** LazyList specialization of foldLeft which allows GC to collect along the
* way.
*
* @tparam B The type of value being accumulated.
* @param z The initial value seeded into the function `op`.
* @param op The operation to perform on successive elements of the `LazyList`.
* @return The accumulated value from successive applications of `op`.
*/
@tailrec
override def foldLeft[B](z: B)(op: (B, A) => B): B =
if (isEmpty) z
else tail.foldLeft(op(z, head))(op)

// LazyList.Empty doesn't use the SerializationProxy
protected[this] def writeReplace(): AnyRef =
if (knownNonEmpty) new SerializationProxy[A](this) else this
Expand Down
1 change: 1 addition & 0 deletions src/reflect/scala/reflect/internal/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1317,6 +1317,7 @@ trait Definitions extends api.StandardDefinitions {
lazy val AnnotationRepeatableAttr = requiredClass[java.lang.annotation.Repeatable]

// Annotations
lazy val NullOutClass = requiredClass[scala.annotation.nullOut]
lazy val ElidableMethodClass = requiredClass[scala.annotation.elidable]
lazy val ImplicitNotFoundClass = requiredClass[scala.annotation.implicitNotFound]
lazy val ImplicitAmbiguousClass = getClassIfDefined("scala.annotation.implicitAmbiguous")
Expand Down
2 changes: 2 additions & 0 deletions src/reflect/scala/reflect/internal/StdAttachments.scala
Original file line number Diff line number Diff line change
Expand Up @@ -186,4 +186,6 @@ trait StdAttachments {

/** Force desugaring Match trees, don't emit switches. Attach to DefDef trees or their symbol. */
case object ForceMatchDesugar extends PlainAttachment

case object NullOutAttachment extends PlainAttachment
}
2 changes: 2 additions & 0 deletions src/reflect/scala/reflect/runtime/JavaUniverseForce.scala
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ trait JavaUniverseForce { self: runtime.JavaUniverse =>
this.DiscardedExpr
this.BooleanParameterType
this.ForceMatchDesugar
this.NullOutAttachment
this.noPrint
this.typeDebug
// inaccessible: this.posAssigner
Expand Down Expand Up @@ -449,6 +450,7 @@ trait JavaUniverseForce { self: runtime.JavaUniverse =>
definitions.AnnotationRetentionAttr
definitions.AnnotationRetentionPolicyAttr
definitions.AnnotationRepeatableAttr
definitions.NullOutClass
definitions.ElidableMethodClass
definitions.ImplicitNotFoundClass
definitions.ImplicitAmbiguousClass
Expand Down
5 changes: 2 additions & 3 deletions test/junit/scala/collection/immutable/LazyListGCTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,8 @@ class LazyListGCTest {
assertLazyListOpAllowsGC((ll, check) => ll.tails.zipWithIndex.foreach { case (_, i) => check(i) }, _ => ())
}

@Test
@Test @annotation.nowarn("cat=deprecation")
def foldLeft(): Unit = {
// fails when using `/:` instead of `foldLeft`
assertLazyListOpAllowsGC((ll, check) => ll.foldLeft(0){ case (s, x) => check(x); s + x}, _ => ())
assertLazyListOpAllowsGC((ll, check) => ll./:(0){ case (s, x) => check(x); s + x}, _ => ())
}
}
36 changes: 36 additions & 0 deletions test/junit/scala/tools/nsc/backend/jvm/BytecodeTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1077,4 +1077,40 @@ class BytecodeTest extends BytecodeTesting {
assertInvoke(getMethod(t, "t5"), "j/J", "k")
assertInvoke(getMethod(t, "t6"), "j/J", "k")
}

@Test def nullOutMixin(): Unit = {
val code =
"""trait T { @annotation.nullOut def meh = 42 }
|class C extends T
|""".stripMargin

val List(c, t) = compileClasses(code)
assertSameSummary(getMethod(c, "meh"), List(ALOAD, ACONST_NULL, ASTORE, "meh$", IRETURN))
assertSameSummary(getMethod(t, "meh$"), List(ALOAD, ACONST_NULL, ASTORE, "meh", IRETURN))
}

@Test def nullOutCall(): Unit = {
val code =
"""class C {
| def f = (this: @annotation.nullOut).hashCode
| def g(x: AnyRef) = (x: @annotation.nullOut).hashCode
|}""".stripMargin
val c = compileClass(code)
assertSameSummary(getMethod(c, "f"), List(ALOAD, ACONST_NULL, ASTORE, "hashCode", IRETURN))
assertSameSummary(getMethod(c, "g"), List(ALOAD, ACONST_NULL, ASTORE, "hashCode", IRETURN))
}

@Test def nullOutBridge(): Unit = {
val code =
"""class C {
| def f: Object = ""
|}
|class D extends C {
| @annotation.nullOut override def f: String = ""
|}
|""".stripMargin
val List(_, d) = compileClasses(code)
val List(bridge) = getMethods(d, "f").filter(_.instructions.exists(_.opcode == INVOKEVIRTUAL))
assertSameSummary(bridge, List(ALOAD, ACONST_NULL, ASTORE, "f", ARETURN))
}
}








ApplySandwichStrip

pFad - (p)hone/(F)rame/(a)nonymizer/(d)eclutterfier!      Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

Fetched URL: http://github.com/scala/scala/pull/10959/files

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy