diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala b/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala index 1eba6c0b1bf8..04b5056efe15 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala @@ -25,6 +25,7 @@ import dotty.tools.dotc.core.Phases.* import dotty.tools.dotc.core.Decorators.em import dotty.tools.dotc.report import dotty.tools.dotc.ast.Trees.SyntheticUnit +import dotty.tools.dotc.transform.PostTyper.lastUseAttachment /* * @@ -249,7 +250,7 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { genLoadTo(elsep, expectedType, dest) else lineNumber(tree.cond) - genAdaptAndSendToDest(UNIT, expectedType, dest) + genAdaptAndSendToDest(UNIT, expectedType, dest, lastUses.contains(tree.symbol)) expectedType end if } @@ -393,6 +394,11 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { } else { mnode.visitVarInsn(asm.Opcodes.ALOAD, 0) + + if tree.hasAttachment(lastUseAttachment) then + emit(asm.Opcodes.ACONST_NULL) + mnode.visitVarInsn(asm.Opcodes.ASTORE, 0) + // When compiling Array.scala, the constructor invokes `Array.this.super.`. 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 @@ -436,7 +442,12 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { case None => if (!sym.is(Package)) { if (sym.is(Module)) genLoadModule(sym) - else locals.load(sym) + else + locals.load(sym) + if t.hasAttachment(lastUseAttachment) then + emit(asm.Opcodes.ACONST_NULL) + val idx = locals.getOrMakeLocal(sym).idx + bc.store(idx, tk) } case Some(t) => genLoad(t, generatedType) @@ -487,10 +498,10 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { // emit conversion and send to the right destination if generatedDest == LoadDestination.FallThrough then - genAdaptAndSendToDest(generatedType, expectedType, dest) + genAdaptAndSendToDest(generatedType, expectedType, dest, lastUses.contains(tree.symbol)) end genLoadTo - def genAdaptAndSendToDest(generatedType: BType, expectedType: BType, dest: LoadDestination): Unit = + def genAdaptAndSendToDest(generatedType: BType, expectedType: BType, dest: LoadDestination, cleanSyntheticCopy: Boolean): Unit = if generatedType != expectedType then adapt(generatedType, expectedType) @@ -507,6 +518,9 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { bc.store(loc.idx, expectedType) bc dropMany stackDiff bc.load(loc.idx, expectedType) + if cleanSyntheticCopy then + emit(asm.Opcodes.ACONST_NULL) + bc.store(loc.idx, expectedType) end if bc goTo label case LoadDestination.Return => @@ -1717,6 +1731,13 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { val lNull = new asm.Label val lNonNull = new asm.Label + def cleanSyntheticCopy = + if lastUses.contains(r.symbol) then + emit(asm.Opcodes.ACONST_NULL) + locals.store(eqEqTempLocal) + + + genLoad(l, ObjectRef) stack.push(ObjectRef) genLoad(r, ObjectRef) @@ -1728,10 +1749,12 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { markProgramPoint(lNull) bc drop ObjectRef locals.load(eqEqTempLocal) + cleanSyntheticCopy genCZJUMP(success, failure, Primitives.EQ, ObjectRef, targetIfNoJump = lNonNull) markProgramPoint(lNonNull) locals.load(eqEqTempLocal) + cleanSyntheticCopy genCallMethod(defn.Any_equals, InvokeStyle.Virtual) genCZJUMP(success, failure, Primitives.NE, BOOL, targetIfNoJump) } diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala index 5390626eb2cc..c81be0e26413 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala @@ -20,6 +20,7 @@ import dotty.tools.dotc.core.Types.* import dotty.tools.dotc.core.Contexts.* import dotty.tools.dotc.util.Spans.* import dotty.tools.dotc.report +import dotty.tools.dotc.transform.PostTyper.methodLastUses /* @@ -432,6 +433,8 @@ trait BCodeSkelBuilder extends BCodeHelpers { val stack = new BTypesStack // line numbers var lastEmittedLineNr = -1 + // lastUse annotated variables + var lastUses = Set.empty[Symbol] object bc extends JCodeMethodN { override def jmethod = PlainSkelBuilder.this.mnode @@ -648,6 +651,7 @@ trait BCodeSkelBuilder extends BCodeHelpers { val rhs = dd.rhs locals.reset(isStaticMethod = methSymbol.isStaticMember) jumpDest = immutable.Map.empty + lastUses = dd.getAttachment(methodLastUses).getOrElse(Set.empty[Symbol]) // check previous invocation of genDefDef exited as many varsInScope as it entered. assert(varsInScope == null, "Unbalanced entering/exiting of GenBCode's genBlock().") diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index f82f7956b34b..d272b70242e4 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -60,6 +60,7 @@ class Compiler { List(new InstrumentCoverage) :: // Perform instrumentation for code coverage (if -coverage-out is set) List(new CrossVersionChecks, // Check issues related to deprecated and experimental new FirstTransform, // Some transformations to put trees into a canonical form + new VerifyLastUseAnnotations, // Well actually it has its own tree traverser, so it does not make that much sense to be here new CheckReentrant, // Internal use only: Check that compiled program has no data races involving global vars new ElimPackagePrefixes, // Eliminate references to package prefixes in Select nodes new CookComments, // Cook the comments: expand variables, doc, etc. @@ -143,7 +144,7 @@ class Compiler { new sjs.JUnitBootstrappers, // Generate JUnit-specific bootstrapper classes for Scala.js (not enabled by default) new CollectEntryPoints, // Collect all entry points and save them in the context new CollectSuperCalls, // Find classes that are called with super - new RepeatableAnnotations) :: // Aggregate repeatable annotations + new RepeatableAnnotations) :: // Aggregate repeatable annotations Nil /** Generate the output of the compilation */ diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index c5dd4662a7b8..fe17cea73049 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1103,6 +1103,7 @@ class Definitions { @tu lazy val PublicInBinaryAnnot: ClassSymbol = requiredClass("scala.annotation.publicInBinary") @tu lazy val WitnessNamesAnnot: ClassSymbol = requiredClass("scala.annotation.internal.WitnessNames") @tu lazy val StableNullAnnot: ClassSymbol = requiredClass("scala.annotation.stableNull") + @tu lazy val LastUseAnnot: ClassSymbol = requiredClass("scala.annotation.lastUse") @tu lazy val JavaRepeatableAnnot: ClassSymbol = requiredClass("java.lang.annotation.Repeatable") diff --git a/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala b/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala index af168b563048..3e40944cab84 100644 --- a/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala +++ b/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala @@ -19,6 +19,8 @@ import util.Store import collection.mutable.{HashMap, LinkedHashMap, ListBuffer} import scala.compiletime.uninitialized +import dotty.tools.dotc.transform.PostTyper.lastUseAttachment +import dotty.tools.dotc.core.Flags object LambdaLift: import ast.tpd.* @@ -284,7 +286,7 @@ class LambdaLift extends MiniPhase with IdentityDenotTransformer { thisPhase => val lft = lifter if (prefix eq NoPrefix) if (sym.enclosure != lft.currentEnclosure && !sym.isStatic) - (if (sym is Method) lft.memberRef(sym) else lft.proxyRef(sym)).withSpan(tree.span) + (if (sym is Method) lft.memberRef(sym) else lft.proxyRef(sym)).withSpan(tree.span) else if (sym.owner.isClass) // sym was lifted out ref(sym).withSpan(tree.span) else diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 9f79c063dc03..1704317b498c 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -22,10 +22,21 @@ import NameKinds.WildcardParamName import cc.* import dotty.tools.dotc.transform.MacroAnnotations.hasMacroAnnotation import dotty.tools.dotc.core.NameKinds.DefaultGetterName +import dotty.tools.dotc.util.Property.{Key, StickyKey} +import dotty.tools.dotc.transform.PostTyper.enclosingMethod +import dotty.tools.dotc.transform.PostTyper.methodLastUses object PostTyper { val name: String = "posttyper" val description: String = "additional checks and cleanups after type checking" + + val lastUseAttachment = StickyKey[Unit] + + val enclosingMethod = Key[DefDef[?]] + + /** Attachment on a Method that stores local variables annotated with @lastUse */ + val methodLastUses = StickyKey[Set[Symbol]] + } /** A macro transform that runs immediately after typer and that performs the following functions: @@ -56,6 +67,8 @@ object PostTyper { * (11) Minimizes `call` fields of `Inlined` nodes to just point to the toplevel * class from which code was inlined. * + * (12) Replaces @lastUse annotation with an attachment, so it survives the erasure phase. + * * The reason for making this a macro transform is that some functions (in particular * super and protected accessors and instantiation checks) are naturally top-down and * don't lend themselves to the bottom-up approach of a mini phase. The other two functions @@ -479,13 +492,14 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => val tree1 = cpy.ValDef(tree)(tpt = makeOverrideTypeDeclared(tree.symbol, tree.tpt)) if tree1.removeAttachment(desugar.UntupledParam).isDefined then checkStableSelection(tree.rhs) + processValOrDefDef(super.transform(tree1)) case tree: DefDef => registerIfHasMacroAnnotations(tree) Checking.checkPolyFunctionType(tree.tpt) annotateContextResults(tree) val tree1 = cpy.DefDef(tree)(tpt = makeOverrideTypeDeclared(tree.symbol, tree.tpt)) - processValOrDefDef(superAcc.wrapDefDef(tree1)(super.transform(tree1).asInstanceOf[DefDef])) + processValOrDefDef(superAcc.wrapDefDef(tree1)(super.transform(tree1)(using ctx.withProperty(enclosingMethod, Some(tree1))).asInstanceOf[DefDef]))//I dont think I need a stickykey since i will never copy the defdef itself when managing its components right ? AND I dont need it in the next phases case tree: TypeDef => registerIfHasMacroAnnotations(tree) val sym = tree.symbol @@ -569,6 +583,19 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => case tree: TypeTree => val tpe = if tree.isInferred then CleanupRetains()(tree.tpe) else tree.tpe tree.withType(transformAnnotsIn(tpe)) + case Typed(t, tpt: TypeTree) if tpt.tpe.hasAnnotation(defn.LastUseAnnot) => + t match + case _: Ident => + t.putAttachment(PostTyper.lastUseAttachment, ()) + val enclosing = ctx.property(enclosingMethod).get + val others = enclosing.getAttachment(methodLastUses).getOrElse(Set.empty) + //attach the symbol to the method + enclosing.putAttachment(methodLastUses, others + t.symbol) + Typed(t, tpt) + case _ => + report.error("`@lastUse` annotation can only be applied on local variables", tree.srcPos) + Typed(t, tpt) + case Typed(Ident(nme.WILDCARD), _) => withMode(Mode.Pattern)(super.transform(tree)) // The added mode signals that bounds in a pattern need not diff --git a/compiler/src/dotty/tools/dotc/transform/VerifyLastUseAnnotations.scala b/compiler/src/dotty/tools/dotc/transform/VerifyLastUseAnnotations.scala new file mode 100644 index 000000000000..78678e3b998e --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/VerifyLastUseAnnotations.scala @@ -0,0 +1,201 @@ +package dotty.tools.dotc +package transform + +import core.* +import ast.tpd.* +import Contexts.* +import MegaPhase.* +import Annotations.* +import Symbols.defn +import Constants.* +import Types.* +import Decorators.* +import Flags.* +import Symbols.Symbol +import dotty.tools.dotc.report + +import scala.collection.mutable +import dotty.tools.dotc.util.SourcePosition +import dotty.tools.dotc.core.StdNames.nme +import dotty.tools.dotc.core.Names.Name +import dotty.tools.dotc.transform.PostTyper.methodLastUses +import dotty.tools.dotc.util.Property +import dotty.tools.dotc.transform.VerifyLastUseAnnotations.{VariableStatus, VariableStatusMap} +import dotty.tools.dotc.transform.PostTyper.lastUseAttachment +import dotty.tools.dotc.transform.VerifyLastUseAnnotations.lastUseVariables +import dotty.tools.dotc.transform.VerifyLastUseAnnotations.capturedVariables + +object VerifyLastUseAnnotations: + val name: String = "verifyLastUseAnnotations" + val description: String = "verify that @lastUse annotations are use correctly" + + case class VariableStatus(allowVar: Boolean, allowAnnot: Boolean, isAfterValDef: Boolean /*find better name*/){ + + def afterAnnot = VariableStatus(false, allowAnnot, isAfterValDef) + def afterValDef = VariableStatus(allowVar, allowAnnot, true) + def enterLoop = VariableStatus(allowVar, !isAfterValDef, isAfterValDef)//when I enter a loop, I only accept an annotation if did not define the variable yet. this means the variable cannot be annotated many times + def merge(other: VariableStatus) = VariableStatus(allowVar && other.allowVar, allowAnnot && other.allowAnnot, isAfterValDef || other.isAfterValDef)//aftervd should not matter a valdef in a branch has no impact on the outside + } + + //TODO find a better name + type VariableStatusMap = Map[Symbol, VariableStatus] + + extension(ss: VariableStatusMap) + def merge(other: VariableStatusMap): VariableStatusMap = + ss.map((sym, state) => (sym -> state.merge(other(sym)))) + + def enterLoop: VariableStatusMap = + ss.map((sym, state) => (sym -> state.enterLoop)) + + + + object VariableStatus { + def start: VariableStatus = VariableStatus(allowVar = true, allowAnnot = true, isAfterValDef = false) + } + + private val lastUseVariables = Property.Key[Set[Symbol]] + private val capturedVariables = mutable.Map.empty[Symbol, Set[Symbol]] //method => lastUse symbols from outside it is using + + +/* + Verifies the use of the @lastUse annotation + following rules are applied: + 1. the variable cannot be reused after being annotated + 2. the variable cannot be annotated in a loop (or in a lambda) TODO modify + 3. exclusive branches (if-else, match cases) do not impact each other. + + the implementation goes as follows (considering nested functions): + we follow all variables with a @lastUse annotation + PrepareForDefDef: the miniphase dives in the deepest method in the tree, storing in context the lastUse variable from enclosing methods ("captured") + transformDefDef: each method (children first) are verified. If it contains a lastUse captured variable, it is marked as such (in "capturedVariables") + + this allows illegal function calls to be detected, but all function definitions stay legal. +*/ +class VerifyLastUseAnnotations extends MiniPhase: + + override def phaseName: String = VerifyLastUseAnnotations.name + override def description: String = VerifyLastUseAnnotations.description + + + //A TreeAccumulator is more expensive than a miniphase transforms, but I need to do custom information transfer and a miniphase is not flexible enough + private class LastUseVerifier(method: DefDef) extends TreeAccumulator[VariableStatusMap]{ + override def apply(accInfo: VariableStatusMap, tree: Tree)(using Context): VariableStatusMap = traverse(tree)(accInfo) + + def verify(using Context): Unit = + method.getAttachment(methodLastUses) match + case None if ctx.property(lastUseVariables).get.nonEmpty => + // enclosing method has lastUse variables => check if this one uses any lastUse variable + traverse(method.rhs)(Map.empty) + case None => () + case Some(symbols) => + val ss = symbols.map(s => (s -> VariableStatus.start)).toMap + traverse(method.rhs)(ss) + + private def checkVarValidity(tree: Tree)(using Context): Unit = + if tree.symbol.is(Lazy) then + report.warning("@lastUse annotation on a lazy val does not work (yet)", tree.sourcePos) + + if (tree.tpe.typeSymbol.isPrimitiveValueClass || tree.symbol.is(Flags.Method)) + report.error("`@lastUse` annotation cannot be used on primitives and methods", tree.sourcePos) + if tree.symbol.owner != method.symbol && !tree.isInstanceOf[This] then + report.error(s"@lastUse annotation can only be used in a local context, ${tree.symbol.owner}, ${method.symbol}", tree.sourcePos) + + + + private def traverseExclusiveCases(cases: List[Tree])(info: VariableStatusMap)(using Context): VariableStatusMap = + cases + .map(c => traverse(c)(info))//traverse all of them with same source info + .reduce((a,b)=> a.merge(b))//merge all info, return it + + private def traverseCaseDefs(casedefs: List[CaseDef])(accInfo: VariableStatusMap)(using Context): VariableStatusMap = + var mergedBodies = accInfo //contains the output after each body, 'ored' together + var info = accInfo + + casedefs.foreach(cd => + info = traverse(cd.pat)(info) + info = traverse(cd.guard)(info) + val afterBody = traverse(cd.body)(info) + + mergedBodies = mergedBodies.merge(afterBody) + ) + + mergedBodies + + private def traverse(tree: Tree)(accInfo: VariableStatusMap)(using Context): VariableStatusMap = { + var info = accInfo//used to mutate and pass the information + val sym = tree.symbol + + //by default, accInfo is propagated in the order of the arguments. so block is already good, and everything that has linear reading + tree match + case vd: ValDef if info.contains(sym) => // definition of a lastUse variable + val info1 = info(sym) + info = info.updated(sym, info1.afterValDef) + foldOver(info, vd) + + case _: Ident | This(_) if info.contains(sym) => //lastUse variable in current method + val info1 = info(sym) + if !info1.allowVar then + report.error(s"reuse of variable ${tree.symbol.name} after @lastUse", tree.sourcePos) //reuse after annot + if tree.hasAttachment(PostTyper.lastUseAttachment) then + checkVarValidity(tree) + if info1.allowAnnot then info.updated(sym, info1.afterAnnot) else //annot here + report.error(s"cannot use @lastUse annotation in a loop", tree.sourcePos) //annot here but illegal + tree.removeAttachment(PostTyper.lastUseAttachment) + info + else + info + + case _: Ident if ctx.property(lastUseVariables).get.contains(sym) =>//lastUse variable from an enclosing method + val others = capturedVariables.getOrElse(method.symbol, Set.empty[Symbol]) + capturedVariables.update(method.symbol, others + sym) + info + + case _: Ident if capturedVariables.contains(sym) =>// calling a method which uses a lastUse variable + val freeVars = capturedVariables.apply(sym) + freeVars.foreach(fv => + info.get(fv) match + case Some(value) => if !value.allowVar then report.error(s"calling function ${sym.name}, using variable ${fv.name} previously annotated with @lastUse", tree.sourcePos) + case None => //lastUse variable is defined in an enclosing method. mark the current method as using it + val others = capturedVariables.getOrElse(method.symbol, Set.empty[Symbol]) + capturedVariables.update(method.symbol, others + fv) + ) + info + + case If(cond, thenp, elsep) => + info = traverse(cond)(info) + val info_branch1 = traverse(thenp)(info)//both branches are exclusive + val info_branch2 = traverse(elsep)(info)//so we combine the info at the end + info_branch1.merge(info_branch2) + + case WhileDo(cond, body) => + info = info.enterLoop + info = traverse(cond)(info) + // cannot be annotated or valdef'd inside the loop => no information to propagate further. + traverse(body)(info) + + case Match(selector, cases) => + info = traverse(selector)(info) + traverseCaseDefs(cases)(info) + + case Try(expr, cases, finalizer) => + info = traverse(expr)(info) + info = traverseCaseDefs(cases)(info) + traverse(finalizer)(info) + + case DefDef(name, paramss, tpt, preRhs) => info//already checked, do not enter it + + case _ => foldOver(info, tree)//visit other trees + } + } + + + override def prepareForDefDef(tree: DefDef)(using Context): Context = + val freeVars = ctx.property(lastUseVariables).getOrElse(Set.empty[Symbol]) ++ tree.getAttachment(methodLastUses).getOrElse(Set.empty[Symbol]) + ctx.withProperty(lastUseVariables, Some(freeVars)) + + + override def transformDefDef(mdef: DefDef)(using Context): Tree = + LastUseVerifier(mdef).verify + mdef + + diff --git a/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala b/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala index 2e48b33ec624..68ea9bb0594c 100644 --- a/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala +++ b/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala @@ -2009,6 +2009,41 @@ class DottyBytecodeTests extends DottyBytecodeTest { assertEquals(expected, clsNode.interfaces.asScala) } } + + @Test def lastUseNullify(): Unit = { + val source = + s""" + |import scala.annotation.lastUse + |class A + |object Foo { + | def foo = + | val x = A() + | println(x: @lastUse) + |}""".stripMargin + + + + + checkBCode(source){ dir => + val fooClass = loadClassNode(dir.lookupName("Foo$.class", directory = false).input) + + val fooMeth = getMethod(fooClass, "foo") + + assertSameCode(fooMeth, List( + TypeOp(NEW, "A"), + Op(DUP), + Invoke(INVOKESPECIAL, "A", "", "()V", itf = false), + VarOp(ASTORE, 1), + Field(GETSTATIC, "scala/Predef$", "MODULE$", "Lscala/Predef$;"), + VarOp(ALOAD, 1), + Op(ACONST_NULL), + VarOp(ASTORE, 1), + Invoke(INVOKEVIRTUAL, "scala/Predef$", "println", "(Ljava/lang/Object;)V", itf = false), + Op(RETURN) + )) + + } + } } object invocationReceiversTestCode { diff --git a/library/src/scala/annotation/lastUse.scala b/library/src/scala/annotation/lastUse.scala new file mode 100644 index 000000000000..66837eecf604 --- /dev/null +++ b/library/src/scala/annotation/lastUse.scala @@ -0,0 +1,8 @@ +package scala.annotation + +import java.lang.annotation.{Target} + +/** An annotation that indicates that an element will never be used again after this evaluation, + and can be removed from the stack by force + */ +final class lastUse extends scala.annotation.StaticAnnotation diff --git a/project/Build.scala b/project/Build.scala index 38d9520d1e87..e87e25fc1a3b 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1142,6 +1142,7 @@ object Build { file(s"${baseDirectory.value}/src/scala/annotation/unroll.scala"), file(s"${baseDirectory.value}/src/scala/annotation/targetName.scala"), file(s"${baseDirectory.value}/src/scala/annotation/stableNull.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/lastUse.scala"), file(s"${baseDirectory.value}/src/scala/deriving/Mirror.scala"), file(s"${baseDirectory.value}/src/scala/compiletime/package.scala"), file(s"${baseDirectory.value}/src/scala/quoted/Type.scala"), @@ -1279,7 +1280,8 @@ object Build { file(s"${baseDirectory.value}/src/scala/annotation/init.scala"), file(s"${baseDirectory.value}/src/scala/annotation/unroll.scala"), file(s"${baseDirectory.value}/src/scala/annotation/targetName.scala"), - file(s"${baseDirectory.value}/src/scala/annotation/stableNull.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/stableNull.scala"), + file(s"${baseDirectory.value}/src/scala/annotation/lastUse.scala"), file(s"${baseDirectory.value}/src/scala/deriving/Mirror.scala"), file(s"${baseDirectory.value}/src/scala/compiletime/package.scala"), file(s"${baseDirectory.value}/src/scala/quoted/Type.scala"), diff --git a/tests/neg/early-reclamation/verifier.scala b/tests/neg/early-reclamation/verifier.scala new file mode 100644 index 000000000000..39b97a355e0d --- /dev/null +++ b/tests/neg/early-reclamation/verifier.scala @@ -0,0 +1,83 @@ +import scala.annotation.lastUse + +class A + +object Verif: + val global = A() + + def test1 = + val a = A() + + println(a: @lastUse) + println(a) // error + + + def test2 = + val a = A() + + if "hi".length() == 1 then + println(a: @lastUse) + else + println("hello") + + println(a) // error + + def test3 = + val a = A() + + while true do + println(a: @lastUse) // error + + def test4 = + val a = A() + List(1).foreach(x => println(a: @lastUse)) // error + println(a) + + def test5 = + val a = A() + for x <- 1 to 10 do + println(a: @lastUse) // error + + def test6 = + val a = A() + + def f = () => a + + f() + + println(a: @lastUse) + + f() // error + + def test7 = + val a = A() + + println(a: @lastUse) + + val f = () => a // error + + def test8 = + val a = A() + + def f = + def g = println(a) + g + + println(a: @lastUse) + + f // error + + def test9 = + val l = List(1,2) + val i = 1 + def f = () => 1 + + val j = i: @lastUse // error + val g = f: @lastUse // error + val g2 = global: @lastUse // error + + def test10 = + val a = A() + + def f = println(a: @lastUse) // error + diff --git a/tests/pos/early-reclamation/verifier.scala b/tests/pos/early-reclamation/verifier.scala new file mode 100644 index 000000000000..c2605df7c7e9 --- /dev/null +++ b/tests/pos/early-reclamation/verifier.scala @@ -0,0 +1,61 @@ +import scala.annotation.lastUse + +class Obj{} + + + +object Verif: + + private def use(o: Obj): Unit = + println(o) + + + def test1 = + val o = Obj() + + use(o) + val y = o + use(o) + use(o: @lastUse) + + def test2 = + val o = Obj() + + use(o) + if "hi".length() == 2 then + use(o: @lastUse) + else + if "hello".length == 3 then + use(o) + else + use(o: @lastUse) + + def test3 = + val o = Obj() + + use(o) + + val x = Some(1) + + x match + case None => + use(o: @lastUse) + case Some(x) => + use(o: @lastUse) + + def test4 = + val o = Obj() + + def test5 = + val o = Obj() + + f() + + use(o) + + def f() = + use(o) + + + + \ No newline at end of file