diff --git a/compiler/src/dotty/tools/dotc/CompilationUnit.scala b/compiler/src/dotty/tools/dotc/CompilationUnit.scala index ef7728387406..110659bba3ee 100644 --- a/compiler/src/dotty/tools/dotc/CompilationUnit.scala +++ b/compiler/src/dotty/tools/dotc/CompilationUnit.scala @@ -16,6 +16,7 @@ import core.Decorators._ import config.{SourceVersion, Feature} import StdNames.nme import scala.annotation.internal.sharable +import transform.MacroAnnotations class CompilationUnit protected (val source: SourceFile) { @@ -45,6 +46,8 @@ class CompilationUnit protected (val source: SourceFile) { */ var needsInlining: Boolean = false + var hasMacroAnnotations: Boolean = false + /** Set to `true` if inliner added anonymous mirrors that need to be completed */ var needsMirrorSupport: Boolean = false @@ -119,6 +122,7 @@ object CompilationUnit { force.traverse(unit1.tpdTree) unit1.needsStaging = force.containsQuote unit1.needsInlining = force.containsInline + unit1.hasMacroAnnotations = force.containsMacroAnnotation } unit1 } @@ -147,6 +151,7 @@ object CompilationUnit { var containsQuote = false var containsInline = false var containsCaptureChecking = false + var containsMacroAnnotation = false def traverse(tree: Tree)(using Context): Unit = { if (tree.symbol.isQuote) containsQuote = true @@ -160,6 +165,9 @@ object CompilationUnit { Feature.handleGlobalLanguageImport(prefix, imported) case _ => case _ => + for annot <- tree.symbol.annotations do + if MacroAnnotations.isMacroAnnotation(annot) then + ctx.compilationUnit.hasMacroAnnotations = true traverseChildren(tree) } } diff --git a/compiler/src/dotty/tools/dotc/config/Printers.scala b/compiler/src/dotty/tools/dotc/config/Printers.scala index ecb189de9bb3..63d616e1ce3d 100644 --- a/compiler/src/dotty/tools/dotc/config/Printers.scala +++ b/compiler/src/dotty/tools/dotc/config/Printers.scala @@ -32,6 +32,7 @@ object Printers { val init = noPrinter val inlining = noPrinter val interactiv = noPrinter + val macroAnnot = noPrinter val matchTypes = noPrinter val nullables = noPrinter val overload = noPrinter diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index bad796df2bd5..dee7ea375465 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -895,6 +895,8 @@ class Definitions { @tu lazy val QuotedTypeModule: Symbol = QuotedTypeClass.companionModule @tu lazy val QuotedTypeModule_of: Symbol = QuotedTypeModule.requiredMethod("of") + @tu lazy val MacroAnnotationClass: ClassSymbol = requiredClass("scala.annotation.MacroAnnotation") + @tu lazy val CanEqualClass: ClassSymbol = getClassIfDefined("scala.Eql").orElse(requiredClass("scala.CanEqual")).asClass def CanEqual_canEqualAny(using Context): TermSymbol = val methodName = if CanEqualClass.name == tpnme.Eql then nme.eqlAny else nme.canEqualAny diff --git a/compiler/src/dotty/tools/dotc/quoted/Interpreter.scala b/compiler/src/dotty/tools/dotc/quoted/Interpreter.scala index 8fb59c8d9df8..2e32bd85eeb1 100644 --- a/compiler/src/dotty/tools/dotc/quoted/Interpreter.scala +++ b/compiler/src/dotty/tools/dotc/quoted/Interpreter.scala @@ -32,7 +32,7 @@ import dotty.tools.dotc.reporting.Message import dotty.tools.repl.AbstractFileClassLoader /** Tree interpreter for metaprogramming constructs */ -abstract class Interpreter(pos: SrcPos, classLoader: ClassLoader)(using Context): +class Interpreter(pos: SrcPos, classLoader: ClassLoader)(using Context): import Interpreter._ import tpd._ @@ -68,7 +68,7 @@ abstract class Interpreter(pos: SrcPos, classLoader: ClassLoader)(using Context) // TODO disallow interpreted method calls as arguments case Call(fn, args) => - if (fn.symbol.isConstructor && fn.symbol.owner.owner.is(Package)) + if (fn.symbol.isConstructor) interpretNew(fn.symbol, args.flatten.map(interpretTree)) else if (fn.symbol.is(Module)) interpretModuleAccess(fn.symbol) @@ -185,8 +185,9 @@ abstract class Interpreter(pos: SrcPos, classLoader: ClassLoader)(using Context) private def interpretModuleAccess(fn: Symbol): Object = loadModule(fn.moduleClass) - private def interpretNew(fn: Symbol, args: => List[Object]): Object = { - val clazz = loadClass(fn.owner.fullName.toString) + private def interpretNew(fn: Symbol, args: List[Object]): Object = { + val className = fn.owner.fullName.mangledString.replaceAll("\\$\\.", "\\$") + val clazz = loadClass(className) val constr = clazz.getConstructor(paramsSig(fn): _*) constr.newInstance(args: _*).asInstanceOf[Object] } @@ -219,10 +220,6 @@ abstract class Interpreter(pos: SrcPos, classLoader: ClassLoader)(using Context) private def loadClass(name: String): Class[?] = try classLoader.loadClass(name) catch { - case _: ClassNotFoundException if ctx.compilationUnit.isSuspendable => - if (ctx.settings.XprintSuspension.value) - report.echo(i"suspension triggered by a dependency on $name", pos) - ctx.compilationUnit.suspend() case MissingClassDefinedInCurrentRun(sym) if ctx.compilationUnit.isSuspendable => if (ctx.settings.XprintSuspension.value) report.echo(i"suspension triggered by a dependency on $sym", pos) @@ -277,13 +274,15 @@ abstract class Interpreter(pos: SrcPos, classLoader: ClassLoader)(using Context) } private object MissingClassDefinedInCurrentRun { - def unapply(targetException: NoClassDefFoundError)(using Context): Option[Symbol] = { - val className = targetException.getMessage - if (className eq null) None - else { - val sym = staticRef(className.toTypeName).symbol - if (sym.isDefinedInCurrentRun) Some(sym) else None - } + def unapply(targetException: Throwable)(using Context): Option[Symbol] = { + targetException match + case _: NoClassDefFoundError | _: ClassNotFoundException => + val className = targetException.getMessage + if className eq null then None + else + val sym = staticRef(className.toTypeName).symbol + if (sym.isDefinedInCurrentRun) Some(sym) else None + case _ => None } } diff --git a/compiler/src/dotty/tools/dotc/transform/Inlining.scala b/compiler/src/dotty/tools/dotc/transform/Inlining.scala index 5ddcf600c63a..a6f41bccfcfb 100644 --- a/compiler/src/dotty/tools/dotc/transform/Inlining.scala +++ b/compiler/src/dotty/tools/dotc/transform/Inlining.scala @@ -7,14 +7,18 @@ import Contexts._ import Symbols._ import SymUtils._ import dotty.tools.dotc.ast.tpd - +import dotty.tools.dotc.ast.Trees._ +import dotty.tools.dotc.quoted._ import dotty.tools.dotc.core.StagingContext._ import dotty.tools.dotc.inlines.Inlines import dotty.tools.dotc.ast.TreeMapWithImplicits +import dotty.tools.dotc.core.DenotTransformers.IdentityDenotTransformer /** Inlines all calls to inline methods that are not in an inline method or a quote */ -class Inlining extends MacroTransform { +class Inlining extends MacroTransform with IdentityDenotTransformer { + thisPhase => + import tpd._ override def phaseName: String = Inlining.name @@ -23,8 +27,10 @@ class Inlining extends MacroTransform { override def allowsImplicitSearch: Boolean = true + override def changesMembers: Boolean = true + override def run(using Context): Unit = - if ctx.compilationUnit.needsInlining then + if ctx.compilationUnit.needsInlining || ctx.compilationUnit.hasMacroAnnotations then try super.run catch case _: CompilationUnit.SuspendException => () @@ -59,8 +65,16 @@ class Inlining extends MacroTransform { private class InliningTreeMap extends TreeMapWithImplicits { override def transform(tree: Tree)(using Context): Tree = { tree match - case tree: DefTree => + case tree: MemberDef => if tree.symbol.is(Inline) then tree + else if tree.symbol.is(Param) then super.transform(tree) + else if + !tree.symbol.isPrimaryConstructor + && StagingContext.level == 0 + && MacroAnnotations.hasMacroAnnotation(tree.symbol) + then + val trees = new MacroAnnotations(thisPhase).expandAnnotations(tree) + flatTree(trees.map(super.transform)) else super.transform(tree) case _: Typed | _: Block => super.transform(tree) diff --git a/compiler/src/dotty/tools/dotc/transform/MacroAnnotations.scala b/compiler/src/dotty/tools/dotc/transform/MacroAnnotations.scala new file mode 100644 index 000000000000..44bc11e90602 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/MacroAnnotations.scala @@ -0,0 +1,117 @@ +package dotty.tools.dotc +package transform + +import scala.language.unsafeNulls + +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.ast.Trees.* +import dotty.tools.dotc.config.Printers.{macroAnnot => debug} +import dotty.tools.dotc.core.Annotations.* +import dotty.tools.dotc.core.Contexts.* +import dotty.tools.dotc.core.Decorators.* +import dotty.tools.dotc.core.DenotTransformers.DenotTransformer +import dotty.tools.dotc.core.Flags.* +import dotty.tools.dotc.core.MacroClassLoader +import dotty.tools.dotc.core.Symbols.* +import dotty.tools.dotc.quoted.* +import dotty.tools.dotc.util.SrcPos +import scala.quoted.runtime.impl.{QuotesImpl, SpliceScope} + +import scala.quoted.Quotes + +class MacroAnnotations(thisPhase: DenotTransformer): + import tpd.* + import MacroAnnotations.* + + /** Expands every macro annotation that is on this tree. + * Returns a list with transformed definition and any added definitions. + */ + def expandAnnotations(tree: MemberDef)(using Context): List[DefTree] = + if !hasMacroAnnotation(tree.symbol) then + List(tree) + else if tree.symbol.is(Module) then + if tree.symbol.isClass then // error only reported on module class + report.error("macro annotations are not supported on object", tree) + List(tree) + else if tree.symbol.isClass then + report.error("macro annotations are not supported on class", tree) + List(tree) + else if tree.symbol.isType then + report.error("macro annotations are not supported on type", tree) + List(tree) + else + debug.println(i"Expanding macro annotations of:\n$tree") + + val macroInterpreter = new Interpreter(tree.srcPos, MacroClassLoader.fromContext) + + val allTrees = List.newBuilder[DefTree] + var insertedAfter: List[List[DefTree]] = Nil + + // Apply all macro annotation to `tree` and collect new definitions in order + val transformedTree: DefTree = tree.symbol.annotations.foldLeft(tree) { (tree, annot) => + if isMacroAnnotation(annot) then + debug.println(i"Expanding macro annotation: ${annot}") + + // Interpret call to `new myAnnot(..).transform(using )()` + val transformedTrees = callMacro(macroInterpreter, tree, annot) + transformedTrees.span(_.symbol != tree.symbol) match + case (prefixed, newTree :: suffixed) => + allTrees ++= prefixed + insertedAfter = suffixed :: insertedAfter + prefixed.foreach(checkAndEnter(_, tree.symbol, annot)) + suffixed.foreach(checkAndEnter(_, tree.symbol, annot)) + newTree + case (Nil, Nil) => + report.error(i"Unexpected `Nil` returned by `(${annot.tree}).transform(..)` during macro expansion", annot.tree.srcPos) + tree + case (_, Nil) => + report.error(i"Transformed tree for ${tree} was not return by `(${annot.tree}).transform(..)` during macro expansion", annot.tree.srcPos) + tree + else + tree + } + + allTrees += transformedTree + insertedAfter.foreach(allTrees.++=) + + val result = allTrees.result() + debug.println(result.map(_.show).mkString("expanded to:\n", "\n", "")) + result + + /** Interpret the code `new annot(..).transform(using )()` */ + private def callMacro(interpreter: Interpreter, tree: MemberDef, annot: Annotation)(using Context): List[MemberDef] = + // TODO: Remove when scala.annaotaion.MacroAnnotation is no longer experimental + import scala.reflect.Selectable.reflectiveSelectable + type MacroAnnotation = { + def transform(using Quotes)(tree: Object/*Erased type of quotes.refelct.Definition*/): List[MemberDef /*quotes.refelct.Definition known to be MemberDef in QuotesImpl*/] + } + + // Interpret macro annotation instantiation `new myAnnot(..)` + val annotInstance = interpreter.interpret[MacroAnnotation](annot.tree).get + // TODO: Remove when scala.annaotaion.MacroAnnotation is no longer experimental + assert(annotInstance.getClass.getClassLoader.loadClass("scala.annotation.MacroAnnotation").isInstance(annotInstance)) + + val quotes = QuotesImpl()(using SpliceScope.contextWithNewSpliceScope(tree.symbol.sourcePos)(using MacroExpansion.context(tree)).withOwner(tree.symbol)) + annotInstance.transform(using quotes)(tree.asInstanceOf[quotes.reflect.Definition]) + + /** Check that this tree can be added by the macro annotation and enter it if needed */ + private def checkAndEnter(newTree: Tree, annotated: Symbol, annot: Annotation)(using Context) = + val sym = newTree.symbol + if sym.isClass then + report.error("Generating classes is not supported", annot.tree) + else if sym.isType then + report.error("Generating type is not supported", annot.tree) + else if sym.owner != annotated.owner then + report.error(i"macro annotation $annot added $sym with an inconsistent owner. Expected it to be owned by ${annotated.owner} but was owned by ${sym.owner}.", annot.tree) + else + sym.enteredAfter(thisPhase) + +object MacroAnnotations: + + /** Is this an annotation that implements `scala.annation.MacroAnnotation` */ + def isMacroAnnotation(annot: Annotation)(using Context): Boolean = + annot.tree.symbol.maybeOwner.derivesFrom(defn.MacroAnnotationClass) + + /** Is this symbol annotated with an annotation that implements `scala.annation.MacroAnnotation` */ + def hasMacroAnnotation(sym: Symbol)(using Context): Boolean = + sym.getAnnotation(defn.MacroAnnotationClass).isDefined diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index a669be3d1e3c..bc86992bdadb 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -375,21 +375,25 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase ) } case tree: ValDef => + registerIfHasMacroAnnotations(tree) checkErasedDef(tree) val tree1 = cpy.ValDef(tree)(rhs = normalizeErasedRhs(tree.rhs, tree.symbol)) if tree1.removeAttachment(desugar.UntupledParam).isDefined then checkStableSelection(tree.rhs) processValOrDefDef(super.transform(tree1)) case tree: DefDef => + registerIfHasMacroAnnotations(tree) checkErasedDef(tree) annotateContextResults(tree) val tree1 = cpy.DefDef(tree)(rhs = normalizeErasedRhs(tree.rhs, tree.symbol)) processValOrDefDef(superAcc.wrapDefDef(tree1)(super.transform(tree1).asInstanceOf[DefDef])) case tree: TypeDef => + registerIfHasMacroAnnotations(tree) val sym = tree.symbol if (sym.isClass) VarianceChecker.check(tree) annotateExperimental(sym) + checkMacroAnnotation(sym) tree.rhs match case impl: Template => for parent <- impl.parents do @@ -483,6 +487,16 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase private def normalizeErasedRhs(rhs: Tree, sym: Symbol)(using Context) = if (sym.isEffectivelyErased) dropInlines.transform(rhs) else rhs + /** Check if the definition has macro annotation and sets `compilationUnit.hasMacroAnnotations` if needed. */ + private def registerIfHasMacroAnnotations(tree: DefTree)(using Context) = + if !Inlines.inInlineMethod && MacroAnnotations.hasMacroAnnotation(tree.symbol) then + ctx.compilationUnit.hasMacroAnnotations = true + + /** Check macro annotations implementations */ + private def checkMacroAnnotation(sym: Symbol)(using Context) = + if sym.derivesFrom(defn.MacroAnnotationClass) && !sym.isStatic then + report.error("classes that extend MacroAnnotation must not be inner/local classes", sym.srcPos) + private def checkErasedDef(tree: ValOrDefDef)(using Context): Unit = if tree.symbol.is(Erased, butNot = Macro) then val tpe = tree.rhs.tpe diff --git a/compiler/src/dotty/tools/dotc/transform/YCheckPositions.scala b/compiler/src/dotty/tools/dotc/transform/YCheckPositions.scala index ba42d826fe82..8080a7c911b3 100644 --- a/compiler/src/dotty/tools/dotc/transform/YCheckPositions.scala +++ b/compiler/src/dotty/tools/dotc/transform/YCheckPositions.scala @@ -61,6 +61,7 @@ class YCheckPositions extends Phase { private def isMacro(call: Tree)(using Context) = call.symbol.is(Macro) || + (call.symbol.isClass && call.tpe.derivesFrom(defn.MacroAnnotationClass)) || // The call of a macro after typer is encoded as a Select while other inlines are Ident // TODO remove this distinction once Inline nodes of expanded macros can be trusted (also in Inliner.inlineCallTrace) (!(ctx.phase <= postTyperPhase) && call.isInstanceOf[Select]) diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index c27a470b3778..d97387679d9b 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -2485,8 +2485,14 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler newMethod(owner, name, tpe, Flags.EmptyFlags, noSymbol) def newMethod(owner: Symbol, name: String, tpe: TypeRepr, flags: Flags, privateWithin: Symbol): Symbol = dotc.core.Symbols.newSymbol(owner, name.toTermName, flags | dotc.core.Flags.Method, tpe, privateWithin) + def newUniqueMethod(owner: Symbol, namePrefix: String, tpe: TypeRepr, flags: Flags, privateWithin: Symbol): Symbol = + val name = NameKinds.UniqueName.fresh(namePrefix.toTermName) + dotc.core.Symbols.newSymbol(owner, name, dotc.core.Flags.PrivateMethod | flags, tpe, privateWithin) def newVal(owner: Symbol, name: String, tpe: TypeRepr, flags: Flags, privateWithin: Symbol): Symbol = dotc.core.Symbols.newSymbol(owner, name.toTermName, flags, tpe, privateWithin) + def newUniqueVal(owner: Symbol, namePrefix: String, tpe: TypeRepr, flags: Flags, privateWithin: Symbol): Symbol = + val name = NameKinds.UniqueName.fresh(namePrefix.toTermName) + dotc.core.Symbols.newSymbol(owner, name, flags, tpe, privateWithin) def newBind(owner: Symbol, name: String, flags: Flags, tpe: TypeRepr): Symbol = dotc.core.Symbols.newSymbol(owner, name.toTermName, flags | Case, tpe) def noSymbol: Symbol = dotc.core.Symbols.NoSymbol diff --git a/library/src/scala/annotation/MacroAnnotation.scala b/library/src/scala/annotation/MacroAnnotation.scala new file mode 100644 index 000000000000..50896cb524d5 --- /dev/null +++ b/library/src/scala/annotation/MacroAnnotation.scala @@ -0,0 +1,72 @@ +// TODO in which package should this class be located? +package scala +package annotation + +import scala.quoted._ + +/** Base trait for macro annotation that will transform a definition */ +@experimental +trait MacroAnnotation extends StaticAnnotation: + + /** Transform the `tree` definition and add other definitions + * + * This method takes as argument the annotated definition. + * It returns a non-empty list containing the modified version of the annotated definition. + * The new tree for the definition must use the original symbol. + * New definitions can be added to the list before or after the transformed definitions, this order + * will be retained. + * + * All definitions in the result must have the same owner. The owner can be recovered from `tree.symbol.owner`. + * + * The result cannot contain `class`, `object` or `type` definition. This limitation will be relaxed in the future. + * + * When developing and testing a macro annotation, you must enable `-Xcheck-macros` and `-Ycheck:all`. + * + * Example: + * ```scala + * import scala.quoted.* + * import scala.collection.mutable + * + * class memoize extends MacroAnnotation: + * def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = + * import quotes.reflect._ + * tree match + * case DefDef(name, TermParamClause(param :: Nil) :: Nil, tpt, Some(rhsTree)) => + * (Ref(param.symbol).asExpr, rhsTree.asExpr) match + * case ('{ $paramRefExpr: t }, '{ $rhsExpr: u }) => + * val cacheSymbol = Symbol.newUniqueVal(tree.symbol.owner, name + "Cache", TypeRepr.of[mutable.Map[t, u]], Flags.Private, Symbol.noSymbol) + * val cacheRhs = '{ mutable.Map.empty[t, u] }.asTerm + * val cacheVal = ValDef(cacheSymbol, Some(cacheRhs)) + * val cacheRefExpr = Ref(cacheSymbol).asExprOf[mutable.Map[t, u]] + * val newRhs = '{ $cacheRefExpr.getOrElseUpdate($paramRefExpr, $rhsExpr) }.asTerm + * val newTree = DefDef.copy(tree)(name, TermParamClause(param :: Nil) :: Nil, tpt, Some(newRhs)) + * List(cacheVal, newTree) + * case _ => + * report.error("Annotation only supported on `def` with a single argument are supported") + * List(tree) + * ``` + * with this macro annotation a user can write + * ```scala sc:nocompile + * @memoize + * def fib(n: Int): Int = + * println(s"compute fib of $n") + * if n <= 1 then n else fib(n - 1) + fib(n - 2) + * ``` + * and the macro will modify the definition to create + * ```scala + * val fibCache = + * scala.collection.mutable.Map.empty[Int, Int] + * def fib(n: Int): Int = + * fibCache.getOrElseUpdate( + * n, + * { + * println(s"compute fib of $n") + * if n <= 1 then n else fib(n - 1) + fib(n - 2) + * } + * ) + * ``` + * + * @param Quotes Implicit instance of Quotes used for tree reflection + * @param tree Tree that will be transformed + */ + def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index 2f4439d44a83..038a97d52069 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -3669,13 +3669,32 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => */ def newMethod(parent: Symbol, name: String, tpe: TypeRepr, flags: Flags, privateWithin: Symbol): Symbol + /** Generates a new method symbol with the given parent, name and type. + * + * To define a member method of a class, use the `newMethod` within the `decls` function of `newClass`. + * + * @param parent The owner of the method + * @param name The name of the method + * @param tpe The type of the method (MethodType, PolyType, ByNameType) + * @param flags extra flags to with which the symbol should be constructed + * @param privateWithin the symbol within which this new method symbol should be private. May be noSymbol. + * + * This symbol starts without an accompanying definition. + * It is the meta-programmer's responsibility to provide exactly one corresponding definition by passing + * this symbol to the DefDef constructor. + * + * @note As a macro can only splice code into the point at which it is expanded, all generated symbols must be + * direct or indirect children of the reflection context's owner. + */ + @experimental def newUniqueMethod(parent: Symbol, namePrefix: String, tpe: TypeRepr, flags: Flags, privateWithin: Symbol): Symbol + /** Generates a new val/var/lazy val symbol with the given parent, name and type. * * This symbol starts without an accompanying definition. * It is the meta-programmer's responsibility to provide exactly one corresponding definition by passing * this symbol to the ValDef constructor. * - * Note: Also see reflect.let + * Note: Also see ValDef.let * * @param parent The owner of the val/var/lazy val * @param name The name of the val/var/lazy val @@ -3687,6 +3706,25 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => */ def newVal(parent: Symbol, name: String, tpe: TypeRepr, flags: Flags, privateWithin: Symbol): Symbol + /** Generates a new val/var/lazy val symbol with the given parent, name prefix and type. + * + * This symbol starts without an accompanying definition. + * It is the meta-programmer's responsibility to provide exactly one corresponding definition by passing + * this symbol to the ValDef constructor. + * + * Note: Also see newVal + * Note: Also see ValDef.let + * + * @param parent The owner of the val/var/lazy val + * @param name The name of the val/var/lazy val + * @param tpe The type of the val/var/lazy val + * @param flags extra flags to with which the symbol should be constructed + * @param privateWithin the symbol within which this new method symbol should be private. May be noSymbol. + * @note As a macro can only splice code into the point at which it is expanded, all generated symbols must be + * direct or indirect children of the reflection context's owner. + */ + @experimental def newUniqueVal(parent: Symbol, namePrefix: String, tpe: TypeRepr, flags: Flags, privateWithin: Symbol): Symbol + /** Generates a pattern bind symbol with the given parent, name and type. * * This symbol starts without an accompanying definition. diff --git a/tests/neg-macros/annot-MacroAnnotation-direct.check b/tests/neg-macros/annot-MacroAnnotation-direct.check new file mode 100644 index 000000000000..580b2bcc7639 --- /dev/null +++ b/tests/neg-macros/annot-MacroAnnotation-direct.check @@ -0,0 +1,6 @@ +-- [E042] Type Error: tests/neg-macros/annot-MacroAnnotation-direct.scala:3:0 ------------------------------------------ +3 |@MacroAnnotation // error + |^^^^^^^^^^^^^^^^ + |MacroAnnotation is a trait; it cannot be instantiated + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-macros/annot-MacroAnnotation-direct.scala b/tests/neg-macros/annot-MacroAnnotation-direct.scala new file mode 100644 index 000000000000..a0024457dc48 --- /dev/null +++ b/tests/neg-macros/annot-MacroAnnotation-direct.scala @@ -0,0 +1,4 @@ +import scala.annotation.MacroAnnotation + +@MacroAnnotation // error +def test = () diff --git a/tests/neg-macros/annot-accessIndirect/Macro_1.scala b/tests/neg-macros/annot-accessIndirect/Macro_1.scala new file mode 100644 index 000000000000..f668f7be9102 --- /dev/null +++ b/tests/neg-macros/annot-accessIndirect/Macro_1.scala @@ -0,0 +1,11 @@ +import scala.annotation.{experimental, MacroAnnotation} +import scala.quoted._ + +@experimental +class hello extends MacroAnnotation { + def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = + import quotes.reflect._ + val helloSymbol = Symbol.newUniqueVal(tree.symbol.owner, "hello", TypeRepr.of[String], Flags.EmptyFlags, Symbol.noSymbol) + val helloVal = ValDef(helloSymbol, Some(Literal(StringConstant("Hello, World!")))) + List(helloVal, tree) +} diff --git a/tests/neg-macros/annot-accessIndirect/Macro_2.scala b/tests/neg-macros/annot-accessIndirect/Macro_2.scala new file mode 100644 index 000000000000..d9ecaad92162 --- /dev/null +++ b/tests/neg-macros/annot-accessIndirect/Macro_2.scala @@ -0,0 +1,18 @@ +import scala.annotation.{experimental, MacroAnnotation} +import scala.quoted._ + +@experimental +class foo extends MacroAnnotation { + def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = + import quotes.reflect._ + val s = '{@hello def foo1(x: Int): Int = x + 1;()}.asTerm + val fooDef = s.asInstanceOf[Inlined].body.asInstanceOf[Block].statements.head.asInstanceOf[DefDef] + val hello = Ref(tree.symbol.owner.declaredFields("hello").head).asExprOf[String] // error + tree match + case DefDef(name, params, tpt, Some(t)) => + val rhs = '{ + ${t.asExprOf[String]} + $hello + }.asTerm + val newDef = DefDef.copy(tree)(name, params, tpt, Some(rhs)) + List(fooDef, newDef) +} diff --git a/tests/neg-macros/annot-accessIndirect/Test.scala b/tests/neg-macros/annot-accessIndirect/Test.scala new file mode 100644 index 000000000000..6e2bbd3d3361 --- /dev/null +++ b/tests/neg-macros/annot-accessIndirect/Test.scala @@ -0,0 +1,3 @@ +class Bar: + @foo def bar(x: String): String = x // error + bar("a") diff --git a/tests/neg-macros/annot-empty-result.check b/tests/neg-macros/annot-empty-result.check new file mode 100644 index 000000000000..6d43c19664cb --- /dev/null +++ b/tests/neg-macros/annot-empty-result.check @@ -0,0 +1,13 @@ + +-- Error: tests/neg-macros/annot-empty-result/Test_2.scala:5:2 --------------------------------------------------------- +5 | @nilAnnot // error + | ^^^^^^^^^ + | Unexpected `Nil` returned by `(new nilAnnot()).transform(..)` during macro expansion +-- Error: tests/neg-macros/annot-empty-result/Test_2.scala:9:4 --------------------------------------------------------- +9 | @nilAnnot // error + | ^^^^^^^^^ + | Unexpected `Nil` returned by `(new nilAnnot()).transform(..)` during macro expansion +-- Error: tests/neg-macros/annot-empty-result/Test_2.scala:1:0 --------------------------------------------------------- +1 |@nilAnnot // error + |^^^^^^^^^ + |Unexpected `Nil` returned by `(new nilAnnot()).transform(..)` during macro expansion diff --git a/tests/neg-macros/annot-empty-result/Macro_1.scala b/tests/neg-macros/annot-empty-result/Macro_1.scala new file mode 100644 index 000000000000..ff3be61c05d2 --- /dev/null +++ b/tests/neg-macros/annot-empty-result/Macro_1.scala @@ -0,0 +1,8 @@ +import scala.annotation.{experimental, MacroAnnotation} +import scala.quoted._ + +@experimental +class nilAnnot extends MacroAnnotation { + def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = + Nil +} diff --git a/tests/neg-macros/annot-empty-result/Test_2.scala b/tests/neg-macros/annot-empty-result/Test_2.scala new file mode 100644 index 000000000000..84beeafecc24 --- /dev/null +++ b/tests/neg-macros/annot-empty-result/Test_2.scala @@ -0,0 +1,11 @@ +@nilAnnot // error +def f1 = 1 + +class B: + @nilAnnot // error + def f2 = 2 + + def test = + @nilAnnot // error + def f3 = 2 + () diff --git a/tests/neg-macros/annot-error-annot.check b/tests/neg-macros/annot-error-annot.check new file mode 100644 index 000000000000..c9f75832e4ad --- /dev/null +++ b/tests/neg-macros/annot-error-annot.check @@ -0,0 +1,97 @@ + +-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:18:6 --------------------------------------------------------- +17 | @error +18 | val vMember: Int = 1 // error + | ^ + | MACRO ERROR +-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:20:11 -------------------------------------------------------- +19 | @error +20 | lazy val lvMember: Int = 1 // error + | ^ + | MACRO ERROR +-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:22:6 --------------------------------------------------------- +21 | @error +22 | def dMember: Int = 1 // error + | ^ + | MACRO ERROR +-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:24:8 --------------------------------------------------------- +23 | @error +24 | given gMember: Int = 1 // error + | ^ + | MACRO ERROR +-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:26:8 --------------------------------------------------------- +25 | @error +26 | given gMember2: Num[Int] with // error: object not supported (TODO support) + | ^ + | macro annotations are not supported on object +27 | def zero = 0 +-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:29:8 --------------------------------------------------------- +28 | @error +29 | given gMember3(using DummyImplicit): Num[Int] with // error: class not supported (TODO support) + | ^ + | macro annotations are not supported on class +30 | def zero = 0 +-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:34:8 --------------------------------------------------------- +33 | @error +34 | val vLocal: Int = 1 // error + | ^ + | MACRO ERROR +-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:36:13 -------------------------------------------------------- +35 | @error +36 | lazy val lvLocal: Int = 1 // error + | ^ + | MACRO ERROR +-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:38:8 --------------------------------------------------------- +37 | @error +38 | def dLocal: Int = 1 // error + | ^ + | MACRO ERROR +-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:40:10 -------------------------------------------------------- +39 | @error +40 | given gLocal: Int = 1 // error + | ^ + | MACRO ERROR +-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:42:10 -------------------------------------------------------- +41 | @error +42 | given gLocal2: Num[Int] with // error: object not supported (TODO support) + | ^ + | macro annotations are not supported on object +43 | def zero = 0 +-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:45:10 -------------------------------------------------------- +44 | @error +45 | given gLocal3(using DummyImplicit): Num[Int] with // error: class not supported (TODO support) + | ^ + | macro annotations are not supported on class +46 | def zero = 0 +-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:2:4 ---------------------------------------------------------- +1 |@error +2 |val vGlobal: Int = 1 // error + |^ + |MACRO ERROR +-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:4:9 ---------------------------------------------------------- +3 |@error +4 |lazy val lvGlobal: Int = 1 // error + |^ + |MACRO ERROR +-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:6:4 ---------------------------------------------------------- +5 |@error +6 |def dGlobal: Int = 1 // error + |^ + |MACRO ERROR +-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:8:6 ---------------------------------------------------------- +7 |@error +8 |given gGlobal: Int = 1 // error + |^ + |MACRO ERROR +-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:10:6 --------------------------------------------------------- + 9 |@error +10 |given gGlobal2: Num[Int] with // error: object not supported (TODO support) + |^ + |macro annotations are not supported on object +11 | def zero = 0 +-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:13:6 --------------------------------------------------------- +12 |@error +13 |given gGlobal3(using DummyImplicit): Num[Int] with // error: class not supported (TODO support) + |^ + |macro annotations are not supported on class +14 | def zero = 0 diff --git a/tests/neg-macros/annot-error-annot/Macro_1.scala b/tests/neg-macros/annot-error-annot/Macro_1.scala new file mode 100644 index 000000000000..d54b69903e02 --- /dev/null +++ b/tests/neg-macros/annot-error-annot/Macro_1.scala @@ -0,0 +1,9 @@ +import scala.annotation.{experimental, MacroAnnotation} +import scala.quoted._ + +@experimental +class error extends MacroAnnotation { + def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = + quotes.reflect.report.error("MACRO ERROR", tree.pos) + List(tree) +} diff --git a/tests/neg-macros/annot-error-annot/Test_2.scala b/tests/neg-macros/annot-error-annot/Test_2.scala new file mode 100644 index 000000000000..f343121a0d4e --- /dev/null +++ b/tests/neg-macros/annot-error-annot/Test_2.scala @@ -0,0 +1,50 @@ +@error +val vGlobal: Int = 1 // error +@error +lazy val lvGlobal: Int = 1 // error +@error +def dGlobal: Int = 1 // error +@error +given gGlobal: Int = 1 // error +@error +given gGlobal2: Num[Int] with // error: object not supported (TODO support) + def zero = 0 +@error +given gGlobal3(using DummyImplicit): Num[Int] with // error: class not supported (TODO support) + def zero = 0 + +class B: + @error + val vMember: Int = 1 // error + @error + lazy val lvMember: Int = 1 // error + @error + def dMember: Int = 1 // error + @error + given gMember: Int = 1 // error + @error + given gMember2: Num[Int] with // error: object not supported (TODO support) + def zero = 0 + @error + given gMember3(using DummyImplicit): Num[Int] with // error: class not supported (TODO support) + def zero = 0 + + def locals: Unit = + @error + val vLocal: Int = 1 // error + @error + lazy val lvLocal: Int = 1 // error + @error + def dLocal: Int = 1 // error + @error + given gLocal: Int = 1 // error + @error + given gLocal2: Num[Int] with // error: object not supported (TODO support) + def zero = 0 + @error + given gLocal3(using DummyImplicit): Num[Int] with // error: class not supported (TODO support) + def zero = 0 + () + +trait Num[T]: + def zero: T diff --git a/tests/neg-macros/annot-nested.scala b/tests/neg-macros/annot-nested.scala new file mode 100644 index 000000000000..4365e41eefff --- /dev/null +++ b/tests/neg-macros/annot-nested.scala @@ -0,0 +1,42 @@ +import scala.annotation.{experimental, MacroAnnotation} +import scala.quoted._ + +class Foo: + @experimental + class void extends MacroAnnotation: // error + def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = List(tree) + + object Bar: + @experimental + class void extends MacroAnnotation: // error + def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = List(tree) + +class Foo2: + @experimental + trait void extends MacroAnnotation // error + + object Bar: + @experimental + trait void extends MacroAnnotation // error + +def test: Unit = + @experimental + class void extends MacroAnnotation: // error + def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = List(tree) + + trait void2 extends MacroAnnotation // error + + new MacroAnnotation {} // error + + () + +val test2: Unit = + @experimental + class void extends MacroAnnotation: // error + def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = List(tree) + + trait void2 extends MacroAnnotation // error + + new MacroAnnotation {} // error + + () diff --git a/tests/neg-macros/annot-on-class.check b/tests/neg-macros/annot-on-class.check new file mode 100644 index 000000000000..54fc01bee2ad --- /dev/null +++ b/tests/neg-macros/annot-on-class.check @@ -0,0 +1,17 @@ + +-- Error: tests/neg-macros/annot-on-class/Test_2.scala:2:6 ------------------------------------------------------------- +1 |@voidAnnot +2 |class A // error + |^ + |macro annotations are not supported on class +-- Error: tests/neg-macros/annot-on-class/Test_2.scala:6:8 ------------------------------------------------------------- +5 | @voidAnnot +6 | class C // error + | ^ + | macro annotations are not supported on class +-- Error: tests/neg-macros/annot-on-class/Test_2.scala:10:10 ----------------------------------------------------------- + 9 | @voidAnnot +10 | class D // error + | ^ + | macro annotations are not supported on class +11 | () diff --git a/tests/neg-macros/annot-on-class/Macro_1.scala b/tests/neg-macros/annot-on-class/Macro_1.scala new file mode 100644 index 000000000000..7468c5a200a6 --- /dev/null +++ b/tests/neg-macros/annot-on-class/Macro_1.scala @@ -0,0 +1,8 @@ +import scala.annotation.{experimental, MacroAnnotation} +import scala.quoted._ + +@experimental +class voidAnnot extends MacroAnnotation { + def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = + List(tree) +} diff --git a/tests/neg-macros/annot-on-class/Test_2.scala b/tests/neg-macros/annot-on-class/Test_2.scala new file mode 100644 index 000000000000..7c2475f29380 --- /dev/null +++ b/tests/neg-macros/annot-on-class/Test_2.scala @@ -0,0 +1,11 @@ +@voidAnnot +class A // error + +class B: + @voidAnnot + class C // error + + def test = + @voidAnnot + class D // error + () diff --git a/tests/neg-macros/annot-on-object.check b/tests/neg-macros/annot-on-object.check new file mode 100644 index 000000000000..277c72b54ff5 --- /dev/null +++ b/tests/neg-macros/annot-on-object.check @@ -0,0 +1,16 @@ + +-- Error: tests/neg-macros/annot-on-object/Test_2.scala:2:7 ------------------------------------------------------------ +1 |@voidAnnot +2 |object A // error + |^ + |macro annotations are not supported on object +-- Error: tests/neg-macros/annot-on-object/Test_2.scala:6:9 ------------------------------------------------------------ +5 | @voidAnnot +6 | object C // error + | ^ + | macro annotations are not supported on object +-- Error: tests/neg-macros/annot-on-object/Test_2.scala:10:11 ---------------------------------------------------------- + 9 | @voidAnnot +10 | object D // error + | ^ + | macro annotations are not supported on object diff --git a/tests/neg-macros/annot-on-object/Macro_1.scala b/tests/neg-macros/annot-on-object/Macro_1.scala new file mode 100644 index 000000000000..7468c5a200a6 --- /dev/null +++ b/tests/neg-macros/annot-on-object/Macro_1.scala @@ -0,0 +1,8 @@ +import scala.annotation.{experimental, MacroAnnotation} +import scala.quoted._ + +@experimental +class voidAnnot extends MacroAnnotation { + def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = + List(tree) +} diff --git a/tests/neg-macros/annot-on-object/Test_2.scala b/tests/neg-macros/annot-on-object/Test_2.scala new file mode 100644 index 000000000000..3e0dac0ea832 --- /dev/null +++ b/tests/neg-macros/annot-on-object/Test_2.scala @@ -0,0 +1,11 @@ +@voidAnnot +object A // error + +object B: + @voidAnnot + object C // error + + def test = + @voidAnnot + object D // error + () \ No newline at end of file diff --git a/tests/neg-macros/annot-on-type.check b/tests/neg-macros/annot-on-type.check new file mode 100644 index 000000000000..3844c3eeebe9 --- /dev/null +++ b/tests/neg-macros/annot-on-type.check @@ -0,0 +1,16 @@ + +-- Error: tests/neg-macros/annot-on-type/Test_2.scala:6:7 -------------------------------------------------------------- +5 | @voidAnnot +6 | type C // error + | ^ + | macro annotations are not supported on type +-- Error: tests/neg-macros/annot-on-type/Test_2.scala:10:9 ------------------------------------------------------------- + 9 | @voidAnnot +10 | type D // error + | ^ + | macro annotations are not supported on type +-- Error: tests/neg-macros/annot-on-type/Test_2.scala:2:5 -------------------------------------------------------------- +1 |@voidAnnot +2 |type A // error + |^ + |macro annotations are not supported on type diff --git a/tests/neg-macros/annot-on-type/Macro_1.scala b/tests/neg-macros/annot-on-type/Macro_1.scala new file mode 100644 index 000000000000..7468c5a200a6 --- /dev/null +++ b/tests/neg-macros/annot-on-type/Macro_1.scala @@ -0,0 +1,8 @@ +import scala.annotation.{experimental, MacroAnnotation} +import scala.quoted._ + +@experimental +class voidAnnot extends MacroAnnotation { + def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = + List(tree) +} diff --git a/tests/neg-macros/annot-on-type/Test_2.scala b/tests/neg-macros/annot-on-type/Test_2.scala new file mode 100644 index 000000000000..4dfe1cc76d42 --- /dev/null +++ b/tests/neg-macros/annot-on-type/Test_2.scala @@ -0,0 +1,11 @@ +@voidAnnot +type A // error + +object B: + @voidAnnot + type C // error + + def test = + @voidAnnot + type D // error + () diff --git a/tests/neg-macros/annot-result-owner.check b/tests/neg-macros/annot-result-owner.check new file mode 100644 index 000000000000..f2431aa73039 --- /dev/null +++ b/tests/neg-macros/annot-result-owner.check @@ -0,0 +1,9 @@ + +-- Error: tests/neg-macros/annot-result-owner/Test_2.scala:1:0 --------------------------------------------------------- +1 |@insertVal // error + |^^^^^^^^^^ + |macro annotation @insertVal added value definitionWithWrongOwner$1 with an inconsistent owner. Expected it to be owned by package object Test_2$package but was owned by method foo. +-- Error: tests/neg-macros/annot-result-owner/Test_2.scala:5:2 --------------------------------------------------------- +5 | @insertVal // error + | ^^^^^^^^^^ + |macro annotation @insertVal added value definitionWithWrongOwner$2 with an inconsistent owner. Expected it to be owned by method bar but was owned by method foo. diff --git a/tests/neg-macros/annot-result-owner/Macro_1.scala b/tests/neg-macros/annot-result-owner/Macro_1.scala new file mode 100644 index 000000000000..559913f0cfb1 --- /dev/null +++ b/tests/neg-macros/annot-result-owner/Macro_1.scala @@ -0,0 +1,11 @@ +import scala.annotation.{experimental, MacroAnnotation} +import scala.quoted._ + +@experimental +class insertVal extends MacroAnnotation: + def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = + import quotes.reflect._ + // Use of wrong owner + val valSym = Symbol.newUniqueVal(tree.symbol, "definitionWithWrongOwner", TypeRepr.of[Unit], Flags.Private, Symbol.noSymbol) + val valDef = ValDef(valSym, Some('{}.asTerm)) + List(valDef, tree) diff --git a/tests/neg-macros/annot-result-owner/Test_2.scala b/tests/neg-macros/annot-result-owner/Test_2.scala new file mode 100644 index 000000000000..5bcebb1ecf76 --- /dev/null +++ b/tests/neg-macros/annot-result-owner/Test_2.scala @@ -0,0 +1,6 @@ +@insertVal // error +def foo(): Unit = () + +def bar = + @insertVal // error + def foo(): Unit = () diff --git a/tests/pos-macros/annot-in-object/Macro_1.scala b/tests/pos-macros/annot-in-object/Macro_1.scala new file mode 100644 index 000000000000..52c5daec1f29 --- /dev/null +++ b/tests/pos-macros/annot-in-object/Macro_1.scala @@ -0,0 +1,12 @@ +import scala.annotation.{experimental, MacroAnnotation} +import scala.quoted._ + +object Foo: + @experimental + class void extends MacroAnnotation: + def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = List(tree) + + object Bar: + @experimental + class void extends MacroAnnotation: + def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = List(tree) diff --git a/tests/pos-macros/annot-in-object/Test_2.scala b/tests/pos-macros/annot-in-object/Test_2.scala new file mode 100644 index 000000000000..4fc43d4f2e41 --- /dev/null +++ b/tests/pos-macros/annot-in-object/Test_2.scala @@ -0,0 +1,3 @@ +@Foo.void +@Foo.Bar.void +def test = 0 diff --git a/tests/pos-macros/annot-suspend/Macro_1.scala b/tests/pos-macros/annot-suspend/Macro_1.scala new file mode 100644 index 000000000000..afbf05e568c7 --- /dev/null +++ b/tests/pos-macros/annot-suspend/Macro_1.scala @@ -0,0 +1,7 @@ +import scala.annotation.{experimental, MacroAnnotation} +import scala.quoted._ + +@experimental +class void extends MacroAnnotation: + def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = + List(tree) diff --git a/tests/pos-macros/annot-suspend/Test_2.scala b/tests/pos-macros/annot-suspend/Test_2.scala new file mode 100644 index 000000000000..ee8529fa4414 --- /dev/null +++ b/tests/pos-macros/annot-suspend/Test_2.scala @@ -0,0 +1,2 @@ +@void +def test = 0 diff --git a/tests/pos-macros/annot-then-inline/Macro_1.scala b/tests/pos-macros/annot-then-inline/Macro_1.scala new file mode 100644 index 000000000000..7af98a372842 --- /dev/null +++ b/tests/pos-macros/annot-then-inline/Macro_1.scala @@ -0,0 +1,14 @@ +import scala.annotation.{experimental, MacroAnnotation} +import scala.quoted._ + +@experimental +class useInlinedIdentity extends MacroAnnotation { + def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = + import quotes.reflect.* + tree match + case DefDef(name, params, tpt, Some(rhs)) => + val newRhs = '{ inlinedIdentity(${rhs.asExpr}) }.asTerm + List(DefDef.copy(tree)(name, params, tpt, Some(newRhs))) +} + +inline def inlinedIdentity(x: Any): x.type = x diff --git a/tests/pos-macros/annot-then-inline/Test_2.scala b/tests/pos-macros/annot-then-inline/Test_2.scala new file mode 100644 index 000000000000..3e72fcaaae1d --- /dev/null +++ b/tests/pos-macros/annot-then-inline/Test_2.scala @@ -0,0 +1,2 @@ +@useInlinedIdentity +def test = 0 diff --git a/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala b/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala index e46d73c70b2b..3d6c1ba81683 100644 --- a/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala +++ b/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala @@ -62,6 +62,9 @@ val experimentalDefinitionInLibrary = Set( // Can be stabilized in 3.3.0 or later. "scala.deriving.Mirror$.fromProductTyped", // This API is a bit convoluted. We may need some more feedback before we can stabilize it. + //// New feature: Macro annotations + "scala.annotation.MacroAnnotation", + //// New APIs: Quotes // Can be stabilized in 3.3.0 (unsure) or later "scala.quoted.Quotes.reflectModule.CompilationInfoModule.XmacroSettings", @@ -71,6 +74,8 @@ val experimentalDefinitionInLibrary = Set( // Need experimental annotation macros to check that design works. "scala.quoted.Quotes.reflectModule.ClassDefModule.apply", "scala.quoted.Quotes.reflectModule.SymbolModule.newClass", + "scala.quoted.Quotes.reflectModule.SymbolModule.newUniqueMethod", + "scala.quoted.Quotes.reflectModule.SymbolModule.newUniqueVal", // New APIs: Lightweight lazy vals. Can be stabilized in 3.3.0 "scala.runtime.LazyVals$.Evaluating", diff --git a/tests/run-macros/annot-annot-order/Macro_1.scala b/tests/run-macros/annot-annot-order/Macro_1.scala new file mode 100644 index 000000000000..0593fbcd5c82 --- /dev/null +++ b/tests/run-macros/annot-annot-order/Macro_1.scala @@ -0,0 +1,16 @@ +import scala.annotation.{experimental, MacroAnnotation} +import scala.quoted._ + +@experimental +class print(msg: String) extends MacroAnnotation: + def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = + import quotes.reflect._ + tree match + case DefDef(name, params, tpt, Some(rhsTree)) => + rhsTree.asExpr match + case '{ $rhsExpr: t } => + val newRhs = '{ println(${Expr(msg)}); $rhsExpr }.asTerm + List(DefDef.copy(tree)(name, params, tpt, Some(newRhs))) + case _ => + report.error("Annotation only supported on `def`") + List(tree) diff --git a/tests/run-macros/annot-annot-order/Test_2.scala b/tests/run-macros/annot-annot-order/Test_2.scala new file mode 100644 index 000000000000..a112c0a2e096 --- /dev/null +++ b/tests/run-macros/annot-annot-order/Test_2.scala @@ -0,0 +1,20 @@ +@print("foo") +def foo(): Unit = () + +@print("foo") @print("foo") +def fooFoo(): Unit = () + +@print("foo") @print("bar") +def fooBar(): Unit = () + +@print("bar") @print("foo") +def barFoo(): Unit = () + +@main def Test = + foo() + println() + fooFoo() + println() + fooBar() + println() + barFoo() diff --git a/tests/run-macros/annot-bind/Macro_1.scala b/tests/run-macros/annot-bind/Macro_1.scala new file mode 100644 index 000000000000..acd8c1a1b5c7 --- /dev/null +++ b/tests/run-macros/annot-bind/Macro_1.scala @@ -0,0 +1,17 @@ +import scala.annotation.{experimental, MacroAnnotation} +import scala.quoted._ + +@experimental +class bind(str: String) extends MacroAnnotation: + def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = + import quotes.reflect._ + tree match + case ValDef(name, tpt, Some(rhsTree)) => + val valSym = Symbol.newUniqueVal(tree.symbol.owner, str, tpt.tpe, Flags.Private, Symbol.noSymbol) + val valDef = ValDef(valSym, Some(rhsTree)) + val newRhs = Ref(valSym) + val newTree = ValDef.copy(tree)(name, tpt, Some(newRhs)) + List(valDef, newTree) + case _ => + report.error("Annotation only supported on `val` with a single argument are supported") + List(tree) diff --git a/tests/run-macros/annot-bind/Test_2.scala b/tests/run-macros/annot-bind/Test_2.scala new file mode 100644 index 000000000000..e30ead520721 --- /dev/null +++ b/tests/run-macros/annot-bind/Test_2.scala @@ -0,0 +1,9 @@ +@bind("a") +val foo: String = "foo" + +@bind("a") @bind("b") +val bar: String = "bar" + +@main def Test = + assert(foo == "foo") + assert(bar == "bar") diff --git a/tests/run-macros/annot-changeVal/Macro_1.scala b/tests/run-macros/annot-changeVal/Macro_1.scala new file mode 100644 index 000000000000..8f865afade5b --- /dev/null +++ b/tests/run-macros/annot-changeVal/Macro_1.scala @@ -0,0 +1,12 @@ +import scala.annotation.experimental +import scala.quoted.* +import scala.annotation.MacroAnnotation + +object ChangeVal: + @experimental + class change(i: Int) extends MacroAnnotation { + def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = + import quotes.reflect.* + tree match + case ValDef(n, t, _) => List(ValDef.copy(tree)(n, t, Some(Literal(IntConstant(i))))) + } diff --git a/tests/run-macros/annot-changeVal/Test_2.scala b/tests/run-macros/annot-changeVal/Test_2.scala new file mode 100644 index 000000000000..6e0e44ad8885 --- /dev/null +++ b/tests/run-macros/annot-changeVal/Test_2.scala @@ -0,0 +1,9 @@ +import ChangeVal._ + +class Bar: + @change(5) + val foo: Int = 3 + +@main def Test = + val t = new Bar + assert(t.foo == 5) diff --git a/tests/run-macros/annot-gen2/Macro_1.scala b/tests/run-macros/annot-gen2/Macro_1.scala new file mode 100644 index 000000000000..ff3055ddd94e --- /dev/null +++ b/tests/run-macros/annot-gen2/Macro_1.scala @@ -0,0 +1,15 @@ +import scala.annotation.{experimental, MacroAnnotation} +import scala.quoted._ + +@experimental +class hello extends MacroAnnotation { + def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = + import quotes.reflect._ + tree match + case DefDef(name, params, tpt, Some(t)) => + val rhs = '{ + ${t.asExprOf[String]} + "hello" + }.asTerm + val newDef = DefDef.copy(tree)(name, params, tpt, Some(rhs)) + List(newDef) +} diff --git a/tests/run-macros/annot-gen2/Macro_2.scala b/tests/run-macros/annot-gen2/Macro_2.scala new file mode 100644 index 000000000000..b0af9ef35f1b --- /dev/null +++ b/tests/run-macros/annot-gen2/Macro_2.scala @@ -0,0 +1,20 @@ +import scala.annotation.{experimental, MacroAnnotation} +import scala.quoted._ + +@experimental +class foo extends MacroAnnotation { + def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = + import quotes.reflect._ + tree match + case DefDef(name, params, tpt, Some(t)) => + val s = Ref(params.head.params.head.symbol).asExprOf[String] + val rhs = '{ + @hello def foo1(s: String): String = ${ + @hello def foo(s: String) = s + "a" + Expr(foo("a")) + } + foo1($s) + }.asTerm + val newDef = DefDef.copy(tree)(name, params, tpt, Some(rhs)) + List(newDef) +} diff --git a/tests/run-macros/annot-gen2/Test_3.scala b/tests/run-macros/annot-gen2/Test_3.scala new file mode 100644 index 000000000000..1a7ca80f6479 --- /dev/null +++ b/tests/run-macros/annot-gen2/Test_3.scala @@ -0,0 +1,5 @@ +class Bar: + @foo def bar(s: String) = s + +@main def Test = + assert((new Bar).bar("bar") == "aahellohello") diff --git a/tests/run-macros/annot-generate/Macro_1.scala b/tests/run-macros/annot-generate/Macro_1.scala new file mode 100644 index 000000000000..f668f7be9102 --- /dev/null +++ b/tests/run-macros/annot-generate/Macro_1.scala @@ -0,0 +1,11 @@ +import scala.annotation.{experimental, MacroAnnotation} +import scala.quoted._ + +@experimental +class hello extends MacroAnnotation { + def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = + import quotes.reflect._ + val helloSymbol = Symbol.newUniqueVal(tree.symbol.owner, "hello", TypeRepr.of[String], Flags.EmptyFlags, Symbol.noSymbol) + val helloVal = ValDef(helloSymbol, Some(Literal(StringConstant("Hello, World!")))) + List(helloVal, tree) +} diff --git a/tests/run-macros/annot-generate/Macro_2.scala b/tests/run-macros/annot-generate/Macro_2.scala new file mode 100644 index 000000000000..33d9460e1ddd --- /dev/null +++ b/tests/run-macros/annot-generate/Macro_2.scala @@ -0,0 +1,16 @@ +import scala.annotation.{experimental, MacroAnnotation} +import scala.quoted._ + +@experimental +class foo extends MacroAnnotation { + def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = + import quotes.reflect._ + tree match + case DefDef(name, params, tpt, Some(t)) => + val rhs = '{ + @hello def foo(x: Int): Int = x + 1 + ${t.asExprOf[Int]} + }.asTerm + val newDef = DefDef.copy(tree)(name, params, tpt, Some(rhs)) + List(newDef) +} diff --git a/tests/run-macros/annot-generate/Test_3.scala b/tests/run-macros/annot-generate/Test_3.scala new file mode 100644 index 000000000000..7077fd544111 --- /dev/null +++ b/tests/run-macros/annot-generate/Test_3.scala @@ -0,0 +1,5 @@ +class Bar: + @foo def bar(x: Int) = x + 1 + +@main def Test = + assert((new Bar).bar(1) == 2) diff --git a/tests/run-macros/annot-memo/Macro_1.scala b/tests/run-macros/annot-memo/Macro_1.scala new file mode 100644 index 000000000000..8cd4ec0cf4c7 --- /dev/null +++ b/tests/run-macros/annot-memo/Macro_1.scala @@ -0,0 +1,22 @@ +import scala.annotation.{experimental, MacroAnnotation} +import scala.quoted._ +import scala.collection.mutable + +@experimental +class memoize extends MacroAnnotation: + def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = + import quotes.reflect._ + tree match + case DefDef(name, TermParamClause(param :: Nil) :: Nil, tpt, Some(rhsTree)) => + (Ref(param.symbol).asExpr, rhsTree.asExpr) match + case ('{ $paramRefExpr: t }, '{ $rhsExpr: u }) => + val cacheSymbol = Symbol.newUniqueVal(tree.symbol.owner, name + "Cache", TypeRepr.of[mutable.Map[t, u]], Flags.Private, Symbol.noSymbol) + val cacheRhs = '{ mutable.Map.empty[t, u] }.asTerm + val cacheVal = ValDef(cacheSymbol, Some(cacheRhs)) + val cacheRefExpr = Ref(cacheSymbol).asExprOf[mutable.Map[t, u]] + val newRhs = '{ $cacheRefExpr.getOrElseUpdate($paramRefExpr, $rhsExpr) }.asTerm + val newTree = DefDef.copy(tree)(name, TermParamClause(param :: Nil) :: Nil, tpt, Some(newRhs)) + List(cacheVal, newTree) + case _ => + report.error("Annotation only supported on `def` with a single argument are supported") + List(tree) diff --git a/tests/run-macros/annot-memo/Test_2.scala b/tests/run-macros/annot-memo/Test_2.scala new file mode 100644 index 000000000000..9b73fd9be7fd --- /dev/null +++ b/tests/run-macros/annot-memo/Test_2.scala @@ -0,0 +1,27 @@ +class Bar: + @memoize + def fib(n: Int): Int = + println(s"compute fib of $n") + if n <= 1 then n + else fib(n - 1) + fib(n - 2) + + @memoize + def fib(n: Long): Long = + println(s"compute fib of $n") + if n <= 1 then n + else fib(n - 1) + fib(n - 2) + + @memoize + def fib(n: Short): Long = + println(s"compute fib of $n") + if n <= 1 then n + else fib(n - 1) + fib(n - 2) + +@main def Test = + val t = new Bar + assert(t.fib(3) == 2) + assert(t.fib(4) == 3) + assert(t.fib(3L) == 2L) + assert(t.fib(4L) == 3L) + assert(t.fib(3: Short) == 2L) + assert(t.fib(4: Short) == 3L) diff --git a/tests/run-macros/annot-result-order.check b/tests/run-macros/annot-result-order.check new file mode 100644 index 000000000000..b9d170c0dac0 --- /dev/null +++ b/tests/run-macros/annot-result-order.check @@ -0,0 +1,10 @@ +before: foo +after: foo +before: bar +before: foo +after: foo +after: bar +before: foo +before: bar +after: bar +after: foo diff --git a/tests/run-macros/annot-result-order/Macro_1.scala b/tests/run-macros/annot-result-order/Macro_1.scala new file mode 100644 index 000000000000..fb0cfad70162 --- /dev/null +++ b/tests/run-macros/annot-result-order/Macro_1.scala @@ -0,0 +1,12 @@ +import scala.annotation.{experimental, MacroAnnotation} +import scala.quoted._ + +@experimental +class print(msg: String) extends MacroAnnotation: + def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = + import quotes.reflect._ + def printMsg(msg: String) = + val valSym = Symbol.newUniqueVal(tree.symbol.owner, tree.symbol.name + "$print$" + msg, TypeRepr.of[Unit], Flags.Private, Symbol.noSymbol) + val valRhs = '{ println(${Expr(msg)}) }.asTerm + ValDef(valSym, Some(valRhs)) + List(printMsg(s"before: $msg"), tree, printMsg(s"after: $msg")) diff --git a/tests/run-macros/annot-result-order/Test_2.scala b/tests/run-macros/annot-result-order/Test_2.scala new file mode 100644 index 000000000000..fce105f3cc6c --- /dev/null +++ b/tests/run-macros/annot-result-order/Test_2.scala @@ -0,0 +1,13 @@ +@print("foo") +def foo(): Unit = () + +@print("foo") @print("bar") +def fooBar(): Unit = () + +@print("bar") @print("foo") +def barFoo(): Unit = () + +@main def Test = + foo() + fooBar() + barFoo() diff --git a/tests/run-macros/annot-simple-fib.check b/tests/run-macros/annot-simple-fib.check new file mode 100644 index 000000000000..2f68352db392 --- /dev/null +++ b/tests/run-macros/annot-simple-fib.check @@ -0,0 +1,5 @@ +compute fib of 3 +compute fib of 2 +compute fib of 1 +compute fib of 0 +compute fib of 4 diff --git a/tests/run-macros/annot-simple-fib/Macro_1.scala b/tests/run-macros/annot-simple-fib/Macro_1.scala new file mode 100644 index 000000000000..03495d19331b --- /dev/null +++ b/tests/run-macros/annot-simple-fib/Macro_1.scala @@ -0,0 +1,26 @@ +import scala.annotation.{experimental, MacroAnnotation} +import scala.quoted._ +import scala.collection.mutable.Map + +@experimental +class memoize extends MacroAnnotation { + def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = + import quotes.reflect._ + tree match + case DefDef(name, params, tpt, Some(fibTree)) => + val cacheRhs = '{Map.empty[Int, Int]}.asTerm + val cacheSymbol = Symbol.newUniqueVal(tree.symbol.owner, name + "Cache", TypeRepr.of[Map[Int, Int]], Flags.EmptyFlags, Symbol.noSymbol) + val cacheVal = ValDef(cacheSymbol, Some(cacheRhs)) + val fibCache = Ref(cacheSymbol).asExprOf[Map[Int, Int]] + val n = Ref(params.head.params.head.symbol).asExprOf[Int] + val rhs = '{ + if $fibCache.contains($n) then + $fibCache($n) + else + val res = ${fibTree.asExprOf[Int]} + $fibCache($n) = res + res + }.asTerm + val newFib = DefDef.copy(tree)(name, params, tpt, Some(rhs)) + List(cacheVal, newFib) +} diff --git a/tests/run-macros/annot-simple-fib/Test_2.scala b/tests/run-macros/annot-simple-fib/Test_2.scala new file mode 100644 index 000000000000..b1bd3fb3e8d0 --- /dev/null +++ b/tests/run-macros/annot-simple-fib/Test_2.scala @@ -0,0 +1,11 @@ +class Bar: + @memoize + def fib(n: Int): Int = + println(s"compute fib of $n") + if n <= 1 then n + else fib(n - 1) + fib(n - 2) + +@main def Test = + val t = new Bar + assert(t.fib(3) == 2) + assert(t.fib(4) == 3)