From f1cf6e72f633616477f079f6973561231ba6a517 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 25 Apr 2022 11:25:24 +0200 Subject: [PATCH 1/3] Add reflect Symbol.info --- .../quoted/runtime/impl/QuotesImpl.scala | 2 + library/src/scala/quoted/Quotes.scala | 4 + .../stdlibExperimentalDefinitions.scala | 1 + tests/run-macros/i11685/Macro_1.scala | 79 +++++++++++++++++++ tests/run-macros/i11685/Test_2.scala | 15 ++++ 5 files changed, 101 insertions(+) create mode 100644 tests/run-macros/i11685/Macro_1.scala create mode 100644 tests/run-macros/i11685/Test_2.scala diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index cd7201ea96aa..8228a3ecd065 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -2519,6 +2519,8 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler def name: String = self.denot.name.toString def fullName: String = self.denot.fullName.toString + def info: TypeRepr = self.denot.info + def pos: Option[Position] = if self.exists then Some(self.sourcePos) else None diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index 875e60ac3d67..5ced0013cf6a 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -3772,6 +3772,10 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => /** The full name of this symbol up to the root package */ def fullName: String + /** Type of the definition */ + @experimental + def info: TypeRepr + /** The position of this symbol */ def pos: Option[Position] diff --git a/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala b/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala index 3d6c1ba81683..5ea1ac302a53 100644 --- a/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala +++ b/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala @@ -76,6 +76,7 @@ val experimentalDefinitionInLibrary = Set( "scala.quoted.Quotes.reflectModule.SymbolModule.newClass", "scala.quoted.Quotes.reflectModule.SymbolModule.newUniqueMethod", "scala.quoted.Quotes.reflectModule.SymbolModule.newUniqueVal", + "scala.quoted.Quotes.reflectModule.SymbolMethods.info", // New APIs: Lightweight lazy vals. Can be stabilized in 3.3.0 "scala.runtime.LazyVals$.Evaluating", diff --git a/tests/run-macros/i11685/Macro_1.scala b/tests/run-macros/i11685/Macro_1.scala new file mode 100644 index 000000000000..08920854a813 --- /dev/null +++ b/tests/run-macros/i11685/Macro_1.scala @@ -0,0 +1,79 @@ +package test + +import scala.quoted.* + +trait Thing { + type Type +} + +object MyMacro { + + def isExpectedReturnType[R: Type](using Quotes): quotes.reflect.Symbol => Boolean = { method => + import quotes.reflect.* + + val expectedReturnType = TypeRepr.of[R] + + method.tree match { + case DefDef(_,_,typedTree,_) => + TypeRepr.of(using typedTree.tpe.asType) <:< expectedReturnType + case _ => false + } + } + + ///TODO no overloads + def checkMethod[R: Type](using q: Quotes)(method: quotes.reflect.Symbol): Option[String] = { + val isExpectedReturnTypeFun = isExpectedReturnType[R] + + Option.when(method.paramSymss.headOption.exists(_.exists(_.isType)))(s"Method ${method.name} has a generic type parameter, this is not supported") orElse + Option.when(!isExpectedReturnTypeFun(method))(s"Method ${method.name} has unexpected return type") + } + + def definedMethodsInType[T: Type](using Quotes): List[quotes.reflect.Symbol] = { + import quotes.reflect.* + + val tree = TypeTree.of[T] + + for { + member <- tree.symbol.methodMembers + //is abstract method, not implemented + if member.flags.is(Flags.Deferred) + + //TODO: is that public? + // TODO? if member.privateWithin + if !member.flags.is(Flags.Private) + if !member.flags.is(Flags.Protected) + if !member.flags.is(Flags.PrivateLocal) + + if !member.isClassConstructor + if !member.flags.is(Flags.Synthetic) + } yield member + } + + transparent inline def client[T, R](r: () => R): T = ${MyMacro.clientImpl[T, R]('r)} + + def clientImpl[T: Type, R: Type](r: Expr[() => R])(using Quotes): Expr[T] = { + import quotes.reflect.* + + val apiType = TypeRepr.of[T] + val tree = TypeTree.of[T] + + val methods = definedMethodsInType[T] + val invalidMethods = methods.flatMap(checkMethod[R]) + if (invalidMethods.nonEmpty) { + report.errorAndAbort(s"Invalid methods: ${invalidMethods.mkString(", ")}") + } + + val className = "_Anon" + val parents = List(TypeTree.of[Object], TypeTree.of[T]) + + def decls(cls: Symbol): List[Symbol] = methods.map { method => + Symbol.newMethod(cls, method.name, method.info, flags = Flags.EmptyFlags /*TODO: method.flags */, privateWithin = method.privateWithin.fold(Symbol.noSymbol)(_.typeSymbol)) + } + + val cls = Symbol.newClass(Symbol.spliceOwner, className, parents = parents.map(_.tpe), decls, selfType = None) + val body = cls.declaredMethods.map { method => DefDef(method, argss => Some('{${r}()}.asTerm)) } + val clsDef = ClassDef(cls, parents, body = body) + val newCls = Typed(Apply(Select(New(TypeIdent(cls)), cls.primaryConstructor), Nil), TypeTree.of[T]) + Block(List(clsDef), newCls).asExprOf[T] + } +} diff --git a/tests/run-macros/i11685/Test_2.scala b/tests/run-macros/i11685/Test_2.scala new file mode 100644 index 000000000000..f8531376dcd8 --- /dev/null +++ b/tests/run-macros/i11685/Test_2.scala @@ -0,0 +1,15 @@ +import test.MyMacro + +trait Command { + def run(): Int + def run2(foo: String): Int + def run3: Int +} + +@main +def Test = { + val myCommand: Command = MyMacro.client[Command, Int](() => 12) + println(myCommand.run()) // 12 + println(myCommand.run2("test")) // 12 + println(myCommand.run3) // 12 +} From fe041d23d7c0618a6c1fb93eaba52c0bb4b2af33 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Fri, 9 Dec 2022 13:51:34 +0100 Subject: [PATCH 2/3] Add reflect fresh names API * Add `Symbol.freshName` * Remove `Symbol.newUniqueMethod` * Remove `Symbol.newUniqueVal` This API is necessary to be able to create new class members without having name clashes. It should also be usable to create new top level definitions that have names that do not clash. This version of unique name can be used for `val`, `def` and `class` symbols. --- .../src/dotty/tools/dotc/core/NameKinds.scala | 1 + .../quoted/runtime/impl/QuotesImpl.scala | 9 ++-- .../scala/annotation/MacroAnnotation.scala | 7 +-- library/src/scala/quoted/Quotes.scala | 51 +++++-------------- .../annot-accessIndirect/Macro_1.scala | 2 +- tests/neg-macros/annot-result-owner.check | 4 +- .../annot-result-owner/Macro_1.scala | 2 +- .../stdlibExperimentalDefinitions.scala | 3 +- tests/run-macros/annot-bind/Macro_1.scala | 2 +- tests/run-macros/annot-generate/Macro_1.scala | 2 +- tests/run-macros/annot-memo/Macro_1.scala | 3 +- tests/run-macros/annot-memo/Test_2.scala | 8 +++ .../annot-result-order/Macro_1.scala | 2 +- .../run-macros/annot-simple-fib/Macro_1.scala | 3 +- .../run-macros/annot-simple-fib/Test_2.scala | 10 ++++ 15 files changed, 51 insertions(+), 58 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/NameKinds.scala b/compiler/src/dotty/tools/dotc/core/NameKinds.scala index f71c16e82b70..d121288a9cd8 100644 --- a/compiler/src/dotty/tools/dotc/core/NameKinds.scala +++ b/compiler/src/dotty/tools/dotc/core/NameKinds.scala @@ -300,6 +300,7 @@ object NameKinds { val UniqueInlineName: UniqueNameKind = new UniqueNameKind("$i") val InlineScrutineeName: UniqueNameKind = new UniqueNameKind("$scrutinee") val InlineBinderName: UniqueNameKind = new UniqueNameKind("$proxy") + val MacroNames: UniqueNameKind = new UniqueNameKind("$macro$") /** A kind of unique extension methods; Unlike other unique names, these can be * unmangled. diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index 8228a3ecd065..7a3b33c4a9f4 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -2485,17 +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 + + def freshName(prefix: String): String = + NameKinds.MacroNames.fresh(prefix.toTermName).toString end Symbol given SymbolMethods: SymbolMethods with diff --git a/library/src/scala/annotation/MacroAnnotation.scala b/library/src/scala/annotation/MacroAnnotation.scala index 7108136cbbb8..599f308b9498 100644 --- a/library/src/scala/annotation/MacroAnnotation.scala +++ b/library/src/scala/annotation/MacroAnnotation.scala @@ -34,7 +34,8 @@ trait MacroAnnotation extends StaticAnnotation: * case DefDef(name, TermParamClause(param :: Nil) :: Nil, tpt, Some(rhsTree)) => * (param.tpt.tpe.asType, tpt.tpe.asType) match * case ('[t], '[u]) => - * val cacheSymbol = Symbol.newUniqueVal(Symbol.spliceOwner, name + "Cache", TypeRepr.of[mutable.Map[t, u]], Flags.Private, Symbol.noSymbol) + * val cacheName = Symbol.freshName(name + "Cache") + * val cacheSymbol = Symbol.newVal(Symbol.spliceOwner, cacheName, TypeRepr.of[mutable.Map[t, u]], Flags.Private, Symbol.noSymbol) * val cacheRhs = * given Quotes = cacheSymbol.asQuotes * '{ mutable.Map.empty[t, u] }.asTerm @@ -60,10 +61,10 @@ trait MacroAnnotation extends StaticAnnotation: * ``` * and the macro will modify the definition to create * ```scala - * val fibCache = + * val fibCache$macro$1 = * scala.collection.mutable.Map.empty[Int, Int] * def fib(n: Int): Int = - * fibCache.getOrElseUpdate( + * fibCache$macro$1.getOrElseUpdate( * n, * { * println(s"compute fib of $n") diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index 5ced0013cf6a..c342a2b1b444 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -3669,25 +3669,6 @@ 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. @@ -3706,25 +3687,6 @@ 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. @@ -3742,6 +3704,18 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => /** Definition not available */ def noSymbol: Symbol + + /** A fresh name for class or member symbol names. + * + * Fresh names are constructed using the following format `prefix + "$macro$" + freshIndex`. + * The `freshIndex` are unique within the current source file. + * + * Examples: See `scala.annotation.MacroAnnotation` + * + * @param prefix Prefix of the fresh name + */ + @experimental + def freshName(prefix: String): String } /** Makes extension methods on `Symbol` available without any imports */ @@ -4415,6 +4389,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => end extension } + /////////////// // POSITIONS // /////////////// diff --git a/tests/neg-macros/annot-accessIndirect/Macro_1.scala b/tests/neg-macros/annot-accessIndirect/Macro_1.scala index 5017a97f6d2a..8679edcfc0c3 100644 --- a/tests/neg-macros/annot-accessIndirect/Macro_1.scala +++ b/tests/neg-macros/annot-accessIndirect/Macro_1.scala @@ -5,7 +5,7 @@ import scala.quoted._ class hello extends MacroAnnotation { def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = import quotes.reflect._ - val helloSymbol = Symbol.newUniqueVal(Symbol.spliceOwner, "hello", TypeRepr.of[String], Flags.EmptyFlags, Symbol.noSymbol) + val helloSymbol = Symbol.newVal(Symbol.spliceOwner, Symbol.freshName("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-result-owner.check b/tests/neg-macros/annot-result-owner.check index f2431aa73039..5d67be058fdf 100644 --- a/tests/neg-macros/annot-result-owner.check +++ b/tests/neg-macros/annot-result-owner.check @@ -2,8 +2,8 @@ -- 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. + |macro annotation @insertVal added value definitionWithWrongOwner$macro$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. + |macro annotation @insertVal added value definitionWithWrongOwner$macro$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 index 559913f0cfb1..34f7541f726b 100644 --- a/tests/neg-macros/annot-result-owner/Macro_1.scala +++ b/tests/neg-macros/annot-result-owner/Macro_1.scala @@ -6,6 +6,6 @@ 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 valSym = Symbol.newVal(tree.symbol, Symbol.freshName("definitionWithWrongOwner"), TypeRepr.of[Unit], Flags.Private, Symbol.noSymbol) val valDef = ValDef(valSym, Some('{}.asTerm)) List(valDef, tree) diff --git a/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala b/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala index 5ea1ac302a53..422d88efc095 100644 --- a/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala +++ b/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala @@ -74,8 +74,7 @@ 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", + "scala.quoted.Quotes.reflectModule.SymbolModule.freshName", "scala.quoted.Quotes.reflectModule.SymbolMethods.info", // New APIs: Lightweight lazy vals. Can be stabilized in 3.3.0 diff --git a/tests/run-macros/annot-bind/Macro_1.scala b/tests/run-macros/annot-bind/Macro_1.scala index 145773a464c5..a0aa69ca356f 100644 --- a/tests/run-macros/annot-bind/Macro_1.scala +++ b/tests/run-macros/annot-bind/Macro_1.scala @@ -7,7 +7,7 @@ class bind(str: String) extends MacroAnnotation: import quotes.reflect._ tree match case ValDef(name, tpt, Some(rhsTree)) => - val valSym = Symbol.newUniqueVal(Symbol.spliceOwner, str, tpt.tpe, Flags.Private, Symbol.noSymbol) + val valSym = Symbol.newVal(Symbol.spliceOwner, Symbol.freshName(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)) diff --git a/tests/run-macros/annot-generate/Macro_1.scala b/tests/run-macros/annot-generate/Macro_1.scala index 5017a97f6d2a..8679edcfc0c3 100644 --- a/tests/run-macros/annot-generate/Macro_1.scala +++ b/tests/run-macros/annot-generate/Macro_1.scala @@ -5,7 +5,7 @@ import scala.quoted._ class hello extends MacroAnnotation { def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = import quotes.reflect._ - val helloSymbol = Symbol.newUniqueVal(Symbol.spliceOwner, "hello", TypeRepr.of[String], Flags.EmptyFlags, Symbol.noSymbol) + val helloSymbol = Symbol.newVal(Symbol.spliceOwner, Symbol.freshName("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-memo/Macro_1.scala b/tests/run-macros/annot-memo/Macro_1.scala index 6316c1b6ca6d..5672bd6358c1 100644 --- a/tests/run-macros/annot-memo/Macro_1.scala +++ b/tests/run-macros/annot-memo/Macro_1.scala @@ -10,7 +10,8 @@ class memoize extends MacroAnnotation: case DefDef(name, TermParamClause(param :: Nil) :: Nil, tpt, Some(rhsTree)) => (param.tpt.tpe.asType, tpt.tpe.asType) match case ('[t], '[u]) => - val cacheSymbol = Symbol.newUniqueVal(Symbol.spliceOwner, name + "Cache", TypeRepr.of[mutable.Map[t, u]], Flags.Private, Symbol.noSymbol) + val cacheName = Symbol.freshName(name + "Cache") + val cacheSymbol = Symbol.newVal(Symbol.spliceOwner, cacheName, TypeRepr.of[mutable.Map[t, u]], Flags.Private, Symbol.noSymbol) val cacheRhs = given Quotes = cacheSymbol.asQuotes '{ mutable.Map.empty[t, u] }.asTerm diff --git a/tests/run-macros/annot-memo/Test_2.scala b/tests/run-macros/annot-memo/Test_2.scala index ae7e07b82b62..10afee3fa8bc 100644 --- a/tests/run-macros/annot-memo/Test_2.scala +++ b/tests/run-macros/annot-memo/Test_2.scala @@ -12,6 +12,14 @@ class Bar: //> else fib(n - 1) + fib(n - 2) //> }) + //> private val fibCache$macro$1: mutable.Map[(n : Int), Int] = mutable.Map.empty[(n : Int), Int] // FIXME: leaks (n : Int) + //> @memoize def fib(n: Int): Int = + //> fibCache$macro$1.getOrElseUpdate(n, { + //> 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") diff --git a/tests/run-macros/annot-result-order/Macro_1.scala b/tests/run-macros/annot-result-order/Macro_1.scala index f802a014e938..5976e27965ef 100644 --- a/tests/run-macros/annot-result-order/Macro_1.scala +++ b/tests/run-macros/annot-result-order/Macro_1.scala @@ -6,7 +6,7 @@ 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(Symbol.spliceOwner, tree.symbol.name + "$print$" + msg, TypeRepr.of[Unit], Flags.Private, Symbol.noSymbol) + val valSym = Symbol.newVal(Symbol.spliceOwner, Symbol.freshName("print"), TypeRepr.of[Unit], Flags.Private, Symbol.noSymbol) val valRhs = given Quotes = valSym.asQuotes '{ println(${Expr(msg)}) }.asTerm diff --git a/tests/run-macros/annot-simple-fib/Macro_1.scala b/tests/run-macros/annot-simple-fib/Macro_1.scala index b0a05e817413..1740f922b63e 100644 --- a/tests/run-macros/annot-simple-fib/Macro_1.scala +++ b/tests/run-macros/annot-simple-fib/Macro_1.scala @@ -8,7 +8,8 @@ class memoize extends MacroAnnotation { import quotes.reflect._ tree match case DefDef(name, params, tpt, Some(fibTree)) => - val cacheSymbol = Symbol.newUniqueVal(Symbol.spliceOwner, name + "Cache", TypeRepr.of[Map[Int, Int]], Flags.EmptyFlags, Symbol.noSymbol) + val cacheName = Symbol.freshName(name + "Cache") + val cacheSymbol = Symbol.newVal(Symbol.spliceOwner, cacheName, TypeRepr.of[Map[Int, Int]], Flags.EmptyFlags, Symbol.noSymbol) val cacheRhs = given Quotes = cacheSymbol.asQuotes '{Map.empty[Int, Int]}.asTerm diff --git a/tests/run-macros/annot-simple-fib/Test_2.scala b/tests/run-macros/annot-simple-fib/Test_2.scala index b1bd3fb3e8d0..7e22982553ac 100644 --- a/tests/run-macros/annot-simple-fib/Test_2.scala +++ b/tests/run-macros/annot-simple-fib/Test_2.scala @@ -4,6 +4,16 @@ class Bar: println(s"compute fib of $n") if n <= 1 then n else fib(n - 1) + fib(n - 2) + //> val fibCache$macro$1: mutable.Map[Int, Int] = mutable.Map.empty[Int, Int] + //> @memoize def fib(n: Int): Int = + //> if fibCache$macro$1.contains(n) then fibCache$macro$1(n) + //> else + //> val res: Int = + //> println(s"compute fib of $n") + //> if n <= 1 then n + //> else fib(n - 1) + fib(n - 2) + //> fibCache$macro$1.update(n, res) + //> res @main def Test = val t = new Bar From 52c94612d0343ac8845af317ef430d01cff12b40 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 28 Nov 2022 10:27:19 +0100 Subject: [PATCH 3/3] Macro annotations class modifications (part 2) Enable modification of classes with `MacroAnnotation`: * Can annotate `class` to transform it * Can annotate `object` to transform the companion class Supported class modifications: * Modify the implementations of `def`, `val`, `var`, `lazy val`, `class`, `object` in the class * Add new `def`, `val`, `var`, `lazy val`, `class`, `object` members to the class * Add a new override for a `def`, `val`, `var`, `lazy val` members in the class Restrictions: * An annotation on a top-level class cannot return a top-level `def`, `val`, `var`, `lazy val` --- .../dotc/transform/MacroAnnotations.scala | 16 +-- .../scala/annotation/MacroAnnotation.scala | 115 +++++++++++++++- tests/neg-macros/annot-error-annot.check | 126 +++++++++++------- .../neg-macros/annot-error-annot/Test_2.scala | 29 +++- .../annot-mod-class-add-top-method.check | 9 ++ .../Macro_1.scala | 17 +++ .../Test_2.scala | 5 + .../annot-mod-class-add-top-val.check | 9 ++ .../annot-mod-class-add-top-val/Macro_1.scala | 16 +++ .../annot-mod-class-add-top-val/Test_2.scala | 5 + tests/neg-macros/annot-on-class.check | 17 --- tests/neg-macros/annot-on-class/Macro_1.scala | 8 -- tests/neg-macros/annot-on-class/Test_2.scala | 11 -- tests/neg-macros/annot-on-object.check | 16 --- .../neg-macros/annot-on-object/Macro_1.scala | 8 -- tests/neg-macros/annot-on-object/Test_2.scala | 11 -- .../annot-mod-class-add-def/Macro_1.scala | 26 ++++ .../annot-mod-class-add-def/Test_2.scala | 14 ++ .../Macro_1.scala | 33 +++++ .../Test_2.scala | 16 +++ .../Macro_1.scala | 25 ++++ .../annot-mod-class-add-lazy-val/Test_2.scala | 14 ++ .../Macro_1.scala | 34 +++++ .../Test_2.scala | 18 +++ .../annot-mod-class-add-val/Macro_1.scala | 25 ++++ .../annot-mod-class-add-val/Test_2.scala | 14 ++ .../annot-mod-class-add-var/Macro_1.scala | 32 +++++ .../annot-mod-class-add-var/Test_2.scala | 26 ++++ .../annot-mod-class-data/Macro_1.scala | 98 ++++++++++++++ .../annot-mod-class-data/Test_2.scala | 31 +++++ .../annot-mod-class-equals/Macro_1.scala | 84 ++++++++++++ .../annot-mod-class-equals/Test_2.scala | 29 ++++ .../annot-mod-class-mod-def/Macro_1.scala | 25 ++++ .../annot-mod-class-mod-def/Test_2.scala | 12 ++ .../annot-mod-class-mod-val/Macro_1.scala | 25 ++++ .../annot-mod-class-mod-val/Test_2.scala | 25 ++++ .../Macro_1.scala | 22 +++ .../annot-mod-class-override-def/Test_2.scala | 12 ++ .../Macro_1.scala | 21 +++ .../annot-mod-class-override-val/Test_2.scala | 23 ++++ .../Macro_1.scala | 18 +++ .../Test_2.scala | 8 ++ 42 files changed, 991 insertions(+), 137 deletions(-) create mode 100644 tests/neg-macros/annot-mod-class-add-top-method.check create mode 100644 tests/neg-macros/annot-mod-class-add-top-method/Macro_1.scala create mode 100644 tests/neg-macros/annot-mod-class-add-top-method/Test_2.scala create mode 100644 tests/neg-macros/annot-mod-class-add-top-val.check create mode 100644 tests/neg-macros/annot-mod-class-add-top-val/Macro_1.scala create mode 100644 tests/neg-macros/annot-mod-class-add-top-val/Test_2.scala delete mode 100644 tests/neg-macros/annot-on-class.check delete mode 100644 tests/neg-macros/annot-on-class/Macro_1.scala delete mode 100644 tests/neg-macros/annot-on-class/Test_2.scala delete mode 100644 tests/neg-macros/annot-on-object.check delete mode 100644 tests/neg-macros/annot-on-object/Macro_1.scala delete mode 100644 tests/neg-macros/annot-on-object/Test_2.scala create mode 100644 tests/run-macros/annot-mod-class-add-def/Macro_1.scala create mode 100644 tests/run-macros/annot-mod-class-add-def/Test_2.scala create mode 100644 tests/run-macros/annot-mod-class-add-inner-class/Macro_1.scala create mode 100644 tests/run-macros/annot-mod-class-add-inner-class/Test_2.scala create mode 100644 tests/run-macros/annot-mod-class-add-lazy-val/Macro_1.scala create mode 100644 tests/run-macros/annot-mod-class-add-lazy-val/Test_2.scala create mode 100644 tests/run-macros/annot-mod-class-add-local-class/Macro_1.scala create mode 100644 tests/run-macros/annot-mod-class-add-local-class/Test_2.scala create mode 100644 tests/run-macros/annot-mod-class-add-val/Macro_1.scala create mode 100644 tests/run-macros/annot-mod-class-add-val/Test_2.scala create mode 100644 tests/run-macros/annot-mod-class-add-var/Macro_1.scala create mode 100644 tests/run-macros/annot-mod-class-add-var/Test_2.scala create mode 100644 tests/run-macros/annot-mod-class-data/Macro_1.scala create mode 100644 tests/run-macros/annot-mod-class-data/Test_2.scala create mode 100644 tests/run-macros/annot-mod-class-equals/Macro_1.scala create mode 100644 tests/run-macros/annot-mod-class-equals/Test_2.scala create mode 100644 tests/run-macros/annot-mod-class-mod-def/Macro_1.scala create mode 100644 tests/run-macros/annot-mod-class-mod-def/Test_2.scala create mode 100644 tests/run-macros/annot-mod-class-mod-val/Macro_1.scala create mode 100644 tests/run-macros/annot-mod-class-mod-val/Test_2.scala create mode 100644 tests/run-macros/annot-mod-class-override-def/Macro_1.scala create mode 100644 tests/run-macros/annot-mod-class-override-def/Test_2.scala create mode 100644 tests/run-macros/annot-mod-class-override-val/Macro_1.scala create mode 100644 tests/run-macros/annot-mod-class-override-val/Test_2.scala create mode 100644 tests/run-macros/annot-mod-class-unused-new-sym/Macro_1.scala create mode 100644 tests/run-macros/annot-mod-class-unused-new-sym/Test_2.scala diff --git a/compiler/src/dotty/tools/dotc/transform/MacroAnnotations.scala b/compiler/src/dotty/tools/dotc/transform/MacroAnnotations.scala index ade94cf15267..712c44bcb66b 100644 --- a/compiler/src/dotty/tools/dotc/transform/MacroAnnotations.scala +++ b/compiler/src/dotty/tools/dotc/transform/MacroAnnotations.scala @@ -33,14 +33,10 @@ class MacroAnnotations(thisPhase: DenotTransformer): 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) + else if tree.symbol.is(Module) && !tree.symbol.isClass then + // only class is transformed 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 + else if tree.symbol.isType && !tree.symbol.isClass then report.error("macro annotations are not supported on type", tree) List(tree) else @@ -126,11 +122,13 @@ class MacroAnnotations(thisPhase: DenotTransformer): 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) + report.error(i"macro annotation returning a `class` is not yet supported. $annot tried to add $sym", annot.tree) else if sym.isType then - report.error("Generating type is not supported", annot.tree) + report.error(i"macro annotation cannot return a `type`. $annot tried to add $sym", 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 if annotated.isClass && annotated.owner.is(Package) /*&& !sym.isClass*/ then + report.error(i"macro annotation can not add top-level ${sym.showKind}. $annot tried to add $sym.", annot.tree) else sym.enteredAfter(thisPhase) diff --git a/library/src/scala/annotation/MacroAnnotation.scala b/library/src/scala/annotation/MacroAnnotation.scala index 599f308b9498..3124edbf1765 100644 --- a/library/src/scala/annotation/MacroAnnotation.scala +++ b/library/src/scala/annotation/MacroAnnotation.scala @@ -18,11 +18,12 @@ trait MacroAnnotation extends StaticAnnotation: * * 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. + * The result cannot add new `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`. + * IMPORTANT: When developing and testing a macro annotation, you must enable `-Xcheck-macros` and `-Ycheck:all`. * - * Example: + * Example 1: + * This example shows how to modify a `def` and add a `val` next to it using a macro annotation. * ```scala * import scala.quoted.* * import scala.collection.mutable @@ -73,6 +74,114 @@ trait MacroAnnotation extends StaticAnnotation: * ) * ``` * + * Example 2: + * This example shows how to modify a `class` using a macro annotation. + * It shows how to override inherited members and add new ones. + * ```scala + * import scala.annotation.{experimental, MacroAnnotation} + * import scala.quoted.* + * + * @experimental + * class equals extends MacroAnnotation: + * def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = + * import quotes.reflect.* + * tree match + * case ClassDef(className, ctr, parents, self, body) => + * val cls = tree.symbol + * + * val constructorParameters = ctr.paramss.collect { case clause: TermParamClause => clause } + * if constructorParameters.size != 1 || constructorParameters.head.params.isEmpty then + * report.errorAndAbort("@equals class must have a single argument list with at least one argument", ctr.pos) + * def checkNotOverridden(sym: Symbol): Unit = + * if sym.overridingSymbol(cls).exists then + * report.error(s"Cannot override ${sym.name} in a @equals class") + * + * val fields = body.collect { + * case vdef: ValDef if vdef.symbol.flags.is(Flags.ParamAccessor) => + * Select(This(cls), vdef.symbol).asExpr + * } + * + * val equalsSym = Symbol.requiredMethod("java.lang.Object.equals") + * checkNotOverridden(equalsSym) + * val equalsOverrideSym = Symbol.newMethod(cls, "equals", equalsSym.info, Flags.Override, Symbol.noSymbol) + * def equalsOverrideDefBody(argss: List[List[Tree]]): Option[Term] = + * given Quotes = equalsOverrideSym.asQuotes + * cls.typeRef.asType match + * case '[c] => + * Some(equalsExpr[c](argss.head.head.asExpr, fields).asTerm) + * val equalsOverrideDef = DefDef(equalsOverrideSym, equalsOverrideDefBody) + * + * val hashSym = Symbol.newVal(cls, Symbol.freshName("hash"), TypeRepr.of[Int], Flags.Private | Flags.Lazy, Symbol.noSymbol) + * val hashVal = ValDef(hashSym, Some(hashCodeExpr(className, fields)(using hashSym.asQuotes).asTerm)) + * + * val hashCodeSym = Symbol.requiredMethod("java.lang.Object.hashCode") + * checkNotOverridden(hashCodeSym) + * val hashCodeOverrideSym = Symbol.newMethod(cls, "hashCode", hashCodeSym.info, Flags.Override, Symbol.noSymbol) + * val hashCodeOverrideDef = DefDef(hashCodeOverrideSym, _ => Some(Ref(hashSym))) + * + * val newBody = equalsOverrideDef :: hashVal :: hashCodeOverrideDef :: body + * List(ClassDef.copy(tree)(className, ctr, parents, self, newBody)) + * case _ => + * report.error("Annotation only supports `class`") + * List(tree) + * + * private def equalsExpr[T: Type](that: Expr[Any], thisFields: List[Expr[Any]])(using Quotes): Expr[Boolean] = + * '{ + * $that match + * case that: T @unchecked => + * ${ + * val thatFields: List[Expr[Any]] = + * import quotes.reflect.* + * thisFields.map(field => Select('{that}.asTerm, field.asTerm.symbol).asExpr) + * thisFields.zip(thatFields) + * .map { case (thisField, thatField) => '{ $thisField == $thatField } } + * .reduce { case (pred1, pred2) => '{ $pred1 && $pred2 } } + * } + * case _ => false + * } + * + * private def hashCodeExpr(className: String, thisFields: List[Expr[Any]])(using Quotes): Expr[Int] = + * '{ + * var acc: Int = ${ Expr(scala.runtime.Statics.mix(-889275714, className.hashCode)) } + * ${ + * Expr.block( + * thisFields.map { + * case '{ $field: Boolean } => '{ if $field then 1231 else 1237 } + * case '{ $field: Byte } => '{ $field.toInt } + * case '{ $field: Char } => '{ $field.toInt } + * case '{ $field: Short } => '{ $field.toInt } + * case '{ $field: Int } => field + * case '{ $field: Long } => '{ scala.runtime.Statics.longHash($field) } + * case '{ $field: Double } => '{ scala.runtime.Statics.doubleHash($field) } + * case '{ $field: Float } => '{ scala.runtime.Statics.floatHash($field) } + * case '{ $field: Null } => '{ 0 } + * case '{ $field: Unit } => '{ 0 } + * case field => '{ scala.runtime.Statics.anyHash($field) } + * }.map(hash => '{ acc = scala.runtime.Statics.mix(acc, $hash) }), + * '{ scala.runtime.Statics.finalizeHash(acc, ${Expr(thisFields.size)}) } + * ) + * } + * } + * ``` + * with this macro annotation a user can write + * ```scala sc:nocompile + * @equals class User(val name: String, val id: Int) + * ``` + * and the macro will modify the class definition to generate the following code + * ```scala + * class User(val name: String, val id: Int): + * override def equals(that: Any): Boolean = + * that match + * case that: User => this.name == that.name && this.id == that.id + * case _ => false + * private lazy val hash$macro$1: Int = + * var acc = 515782504 // scala.runtime.Statics.mix(-889275714, "User".hashCode) + * acc = scala.runtime.Statics.mix(acc, scala.runtime.Statics.anyHash(name)) + * acc = scala.runtime.Statics.mix(acc, id) + * scala.runtime.Statics.finalizeHash(acc, 2) + * override def hashCode(): Int = hash$macro$1 + * ``` + * * @param Quotes Implicit instance of Quotes used for tree reflection * @param tree Tree that will be transformed */ diff --git a/tests/neg-macros/annot-error-annot.check b/tests/neg-macros/annot-error-annot.check index c9f75832e4ad..f150b4561e2c 100644 --- a/tests/neg-macros/annot-error-annot.check +++ b/tests/neg-macros/annot-error-annot.check @@ -1,68 +1,98 @@ --- Error: tests/neg-macros/annot-error-annot/Test_2.scala:18:6 --------------------------------------------------------- -17 | @error -18 | val vMember: Int = 1 // error +-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:17:6 --------------------------------------------------------- +16 |@error +17 |class cGlobal // error + |^ + |MACRO ERROR +-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:20:7 --------------------------------------------------------- +19 |@error +20 |object oGlobal // error + |^ + |MACRO ERROR +-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:24:6 --------------------------------------------------------- +23 | @error +24 | 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 +-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:26:11 -------------------------------------------------------- +25 | @error +26 | 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 +-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:28:6 --------------------------------------------------------- +27 | @error +28 | 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 +-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:30:8 --------------------------------------------------------- +29 | @error +30 | 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) +-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:32:8 --------------------------------------------------------- +31 | @error +32 | given gMember2: Num[Int] with // error + | ^ + | MACRO ERROR +33 | def zero = 0 +-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:35:8 --------------------------------------------------------- +34 | @error +35 | given gMember3(using DummyImplicit): Num[Int] with // error + | ^ + | MACRO ERROR +36 | def zero = 0 +-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:39:8 --------------------------------------------------------- +38 | @error +39 | class cMember // error | ^ - | 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 ERROR +-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:42:9 --------------------------------------------------------- +41 | @error +42 | object oMember // error | ^ - | 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:46:8 --------------------------------------------------------- +45 | @error +46 | 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 +-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:48:13 -------------------------------------------------------- +47 | @error +48 | 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 +-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:50:8 --------------------------------------------------------- +49 | @error +50 | 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 +-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:52:10 -------------------------------------------------------- +51 | @error +52 | 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) +-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:54:10 -------------------------------------------------------- +53 | @error +54 | given gLocal2: Num[Int] with // error | ^ - | 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 ERROR +55 | def zero = 0 +-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:57:10 -------------------------------------------------------- +56 | @error +57 | given gLocal3(using DummyImplicit): Num[Int] with // error | ^ - | macro annotations are not supported on class -46 | def zero = 0 + | MACRO ERROR +58 | def zero = 0 +-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:61:10 -------------------------------------------------------- +60 | @error +61 | class cLocal // error + | ^ + | MACRO ERROR +-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:63:11 -------------------------------------------------------- +62 | @error +63 | object oLocal // error + | ^ + | MACRO ERROR -- Error: tests/neg-macros/annot-error-annot/Test_2.scala:2:4 ---------------------------------------------------------- 1 |@error 2 |val vGlobal: Int = 1 // error @@ -85,13 +115,13 @@ |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) +10 |given gGlobal2: Num[Int] with // error |^ - |macro annotations are not supported on object + |MACRO ERROR 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) +13 |given gGlobal3(using DummyImplicit): Num[Int] with // error |^ - |macro annotations are not supported on class + |MACRO ERROR 14 | def zero = 0 diff --git a/tests/neg-macros/annot-error-annot/Test_2.scala b/tests/neg-macros/annot-error-annot/Test_2.scala index f343121a0d4e..3325ba431127 100644 --- a/tests/neg-macros/annot-error-annot/Test_2.scala +++ b/tests/neg-macros/annot-error-annot/Test_2.scala @@ -7,12 +7,18 @@ def dGlobal: Int = 1 // error @error given gGlobal: Int = 1 // error @error -given gGlobal2: Num[Int] with // error: object not supported (TODO support) +given gGlobal2: Num[Int] with // error def zero = 0 @error -given gGlobal3(using DummyImplicit): Num[Int] with // error: class not supported (TODO support) +given gGlobal3(using DummyImplicit): Num[Int] with // error def zero = 0 +@error +class cGlobal // error + +@error +object oGlobal // error + class B: @error val vMember: Int = 1 // error @@ -23,12 +29,18 @@ class B: @error given gMember: Int = 1 // error @error - given gMember2: Num[Int] with // error: object not supported (TODO support) + given gMember2: Num[Int] with // error def zero = 0 @error - given gMember3(using DummyImplicit): Num[Int] with // error: class not supported (TODO support) + given gMember3(using DummyImplicit): Num[Int] with // error def zero = 0 + @error + class cMember // error + + @error + object oMember // error + def locals: Unit = @error val vLocal: Int = 1 // error @@ -39,11 +51,16 @@ class B: @error given gLocal: Int = 1 // error @error - given gLocal2: Num[Int] with // error: object not supported (TODO support) + given gLocal2: Num[Int] with // error def zero = 0 @error - given gLocal3(using DummyImplicit): Num[Int] with // error: class not supported (TODO support) + given gLocal3(using DummyImplicit): Num[Int] with // error def zero = 0 + + @error + class cLocal // error + @error + object oLocal // error () trait Num[T]: diff --git a/tests/neg-macros/annot-mod-class-add-top-method.check b/tests/neg-macros/annot-mod-class-add-top-method.check new file mode 100644 index 000000000000..28fb93bb29db --- /dev/null +++ b/tests/neg-macros/annot-mod-class-add-top-method.check @@ -0,0 +1,9 @@ + +-- Error: tests/neg-macros/annot-mod-class-add-top-method/Test_2.scala:1:0 --------------------------------------------- +1 |@addTopLevelMethod // error + |^^^^^^^^^^^^^^^^^^ + |macro annotation can not add top-level method. @addTopLevelMethod tried to add method toLevelMethod$macro$1. +-- Error: tests/neg-macros/annot-mod-class-add-top-method/Test_2.scala:4:0 --------------------------------------------- +4 |@addTopLevelMethod // error + |^^^^^^^^^^^^^^^^^^ + |macro annotation can not add top-level method. @addTopLevelMethod tried to add method toLevelMethod$macro$2. diff --git a/tests/neg-macros/annot-mod-class-add-top-method/Macro_1.scala b/tests/neg-macros/annot-mod-class-add-top-method/Macro_1.scala new file mode 100644 index 000000000000..b5c49695ad2a --- /dev/null +++ b/tests/neg-macros/annot-mod-class-add-top-method/Macro_1.scala @@ -0,0 +1,17 @@ +import scala.annotation.{experimental, MacroAnnotation} +import scala.quoted._ +import scala.collection.mutable + +@experimental +class addTopLevelMethod extends MacroAnnotation: + def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = + import quotes.reflect._ + tree match + case ClassDef(name, ctr, parents, self, body) => + val methType = MethodType(Nil)(_ => Nil, _ => TypeRepr.of[Int]) + val methSym = Symbol.newMethod(Symbol.spliceOwner, Symbol.freshName("toLevelMethod"), methType, Flags.EmptyFlags, Symbol.noSymbol) + val methDef = DefDef(methSym, _ => Some(Literal(IntConstant(1)))) + List(methDef, tree) + case _ => + report.error("Annotation only supports `class`") + List(tree) diff --git a/tests/neg-macros/annot-mod-class-add-top-method/Test_2.scala b/tests/neg-macros/annot-mod-class-add-top-method/Test_2.scala new file mode 100644 index 000000000000..eadeff0f060c --- /dev/null +++ b/tests/neg-macros/annot-mod-class-add-top-method/Test_2.scala @@ -0,0 +1,5 @@ +@addTopLevelMethod // error +class Foo + +@addTopLevelMethod // error +object Foo diff --git a/tests/neg-macros/annot-mod-class-add-top-val.check b/tests/neg-macros/annot-mod-class-add-top-val.check new file mode 100644 index 000000000000..bc21173923f1 --- /dev/null +++ b/tests/neg-macros/annot-mod-class-add-top-val.check @@ -0,0 +1,9 @@ + +-- Error: tests/neg-macros/annot-mod-class-add-top-val/Test_2.scala:1:0 ------------------------------------------------ +1 |@addTopLevelVal // error + |^^^^^^^^^^^^^^^ + |macro annotation can not add top-level value. @addTopLevelVal tried to add value toLevelVal$macro$1. +-- Error: tests/neg-macros/annot-mod-class-add-top-val/Test_2.scala:4:0 ------------------------------------------------ +4 |@addTopLevelVal // error + |^^^^^^^^^^^^^^^ + |macro annotation can not add top-level value. @addTopLevelVal tried to add value toLevelVal$macro$2. diff --git a/tests/neg-macros/annot-mod-class-add-top-val/Macro_1.scala b/tests/neg-macros/annot-mod-class-add-top-val/Macro_1.scala new file mode 100644 index 000000000000..c6f21e181879 --- /dev/null +++ b/tests/neg-macros/annot-mod-class-add-top-val/Macro_1.scala @@ -0,0 +1,16 @@ +import scala.annotation.{experimental, MacroAnnotation} +import scala.quoted._ +import scala.collection.mutable + +@experimental +class addTopLevelVal extends MacroAnnotation: + def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = + import quotes.reflect._ + tree match + case ClassDef(name, ctr, parents, self, body) => + val valSym = Symbol.newVal(Symbol.spliceOwner, Symbol.freshName("toLevelVal"), TypeRepr.of[Int], Flags.EmptyFlags, Symbol.noSymbol) + val valDef = ValDef(valSym, Some(Literal(IntConstant(1)))) + List(valDef, tree) + case _ => + report.error("Annotation only supports `class`") + List(tree) diff --git a/tests/neg-macros/annot-mod-class-add-top-val/Test_2.scala b/tests/neg-macros/annot-mod-class-add-top-val/Test_2.scala new file mode 100644 index 000000000000..440e90bc1652 --- /dev/null +++ b/tests/neg-macros/annot-mod-class-add-top-val/Test_2.scala @@ -0,0 +1,5 @@ +@addTopLevelVal // error +class Foo + +@addTopLevelVal // error +object Foo diff --git a/tests/neg-macros/annot-on-class.check b/tests/neg-macros/annot-on-class.check deleted file mode 100644 index 54fc01bee2ad..000000000000 --- a/tests/neg-macros/annot-on-class.check +++ /dev/null @@ -1,17 +0,0 @@ - --- 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 deleted file mode 100644 index 7468c5a200a6..000000000000 --- a/tests/neg-macros/annot-on-class/Macro_1.scala +++ /dev/null @@ -1,8 +0,0 @@ -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 deleted file mode 100644 index 7c2475f29380..000000000000 --- a/tests/neg-macros/annot-on-class/Test_2.scala +++ /dev/null @@ -1,11 +0,0 @@ -@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 deleted file mode 100644 index 277c72b54ff5..000000000000 --- a/tests/neg-macros/annot-on-object.check +++ /dev/null @@ -1,16 +0,0 @@ - --- 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 deleted file mode 100644 index 7468c5a200a6..000000000000 --- a/tests/neg-macros/annot-on-object/Macro_1.scala +++ /dev/null @@ -1,8 +0,0 @@ -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 deleted file mode 100644 index 3e0dac0ea832..000000000000 --- a/tests/neg-macros/annot-on-object/Test_2.scala +++ /dev/null @@ -1,11 +0,0 @@ -@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/run-macros/annot-mod-class-add-def/Macro_1.scala b/tests/run-macros/annot-mod-class-add-def/Macro_1.scala new file mode 100644 index 000000000000..cec3a60350bc --- /dev/null +++ b/tests/run-macros/annot-mod-class-add-def/Macro_1.scala @@ -0,0 +1,26 @@ +import scala.annotation.{experimental, MacroAnnotation} +import scala.quoted._ +import scala.collection.mutable + +@experimental +class addIndirectToString(msg: String) extends MacroAnnotation: + def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = + import quotes.reflect._ + tree match + case ClassDef(name, ctr, parents, self, body) => + val cls = tree.symbol + val stringMethType = ByNameType.apply(TypeRepr.of[String]) + val stringSym = Symbol.newMethod(cls, Symbol.freshName("string"), stringMethType, Flags.Private, Symbol.noSymbol) + val stringDef = DefDef(stringSym, _ => Some(Literal(StringConstant(msg)))) + + val toStringMethType = Symbol.requiredMethod("java.lang.Object.toString").info + val toStringOverrideSym = Symbol.newMethod(cls, "toString", toStringMethType, Flags.Override, Symbol.noSymbol) + val toStringDef = DefDef(toStringOverrideSym, _ => Some(Ref(stringSym))) + + + val newClassDef = ClassDef.copy(tree)(name, ctr, parents, self, stringDef :: toStringDef :: body) + List(newClassDef) + + case _ => + report.error("Annotation only supports `class`") + List(tree) diff --git a/tests/run-macros/annot-mod-class-add-def/Test_2.scala b/tests/run-macros/annot-mod-class-add-def/Test_2.scala new file mode 100644 index 000000000000..05596baee900 --- /dev/null +++ b/tests/run-macros/annot-mod-class-add-def/Test_2.scala @@ -0,0 +1,14 @@ +@addIndirectToString("This is Foo") +class Foo + //> private def string$macro$1: String = "This is Foo" + //> def toString(): String = string$macro$1 + +@addIndirectToString("This is Foo object") +object Foo + //> private def string$macro$2: String = "This is Foo object" + //> def toString(): String = string$macro$2 + +@main def Test(): Unit = + val foo = new Foo + assert(foo.toString() == "This is Foo", foo) + assert(Foo.toString() == "This is Foo object", Foo) diff --git a/tests/run-macros/annot-mod-class-add-inner-class/Macro_1.scala b/tests/run-macros/annot-mod-class-add-inner-class/Macro_1.scala new file mode 100644 index 000000000000..cb98fc75ea74 --- /dev/null +++ b/tests/run-macros/annot-mod-class-add-inner-class/Macro_1.scala @@ -0,0 +1,33 @@ +import scala.annotation.{experimental, MacroAnnotation} +import scala.quoted._ +import scala.collection.mutable + +@experimental +class addInnerClass extends MacroAnnotation: + def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = + import quotes.reflect._ + tree match + case ClassDef(name, ctr, parents, self, body) => + val cls = tree.symbol + + def showClassDecls(showCls: Symbol): List[Symbol] = + List(Symbol.newMethod(showCls, "showMe", MethodType(List("x"))(_ => List(cls.typeRef), _ => TypeRepr.of[String]))) + val parents = List(TypeTree.of[Object]) + val showClassSym = Symbol.newClass(cls, Symbol.freshName("Show"), parents.map(_.tpe), showClassDecls, None) + val showMeSym = showClassSym.declaredMethod("showMe").head + + val showMeDef = DefDef(showMeSym, argss => Some('{ "showMe: " + ${argss.head.head.asExpr}.getClass }.asTerm)) + val showClass = ClassDef(showClassSym, parents, body = List(showMeDef)) + val newShow = Apply(Select(New(TypeIdent(showClassSym)), showClassSym.primaryConstructor), Nil) + val newShowCallShowMe = Apply(Select(newShow, showMeSym), List(This(cls))) + + val toStringMethType = Symbol.requiredMethod("java.lang.Object.toString").info + val toStringOverrideSym = Symbol.newMethod(cls, "toString", toStringMethType, Flags.Override, Symbol.noSymbol) + val toStringDef = DefDef(toStringOverrideSym, _ => Some(newShowCallShowMe)) + + val newClassDef = ClassDef.copy(tree)(name, ctr, parents, self, showClass :: toStringDef :: body) + List(newClassDef) + + case _ => + report.error("Annotation only supports `class`") + List(tree) diff --git a/tests/run-macros/annot-mod-class-add-inner-class/Test_2.scala b/tests/run-macros/annot-mod-class-add-inner-class/Test_2.scala new file mode 100644 index 000000000000..d06d13c439e6 --- /dev/null +++ b/tests/run-macros/annot-mod-class-add-inner-class/Test_2.scala @@ -0,0 +1,16 @@ +@addInnerClass +class Foo + //> class Show: + //> def showMe(x: Foo): String = "showMe: " + x.getClass + //> def toString(): String = (new Show).showMe(this) + +@addInnerClass +object Bar + //> class Show: + //> def showMe(x: Foo): String = "showMe: " + x.getClass + //> def toString(): String = (new Show).showMe(this) + +@main def Test(): Unit = + val foo = new Foo + assert(foo.toString() == "showMe: class Foo", foo) + assert(Bar.toString() == "showMe: class Bar$", Bar) diff --git a/tests/run-macros/annot-mod-class-add-lazy-val/Macro_1.scala b/tests/run-macros/annot-mod-class-add-lazy-val/Macro_1.scala new file mode 100644 index 000000000000..ff2f67a41bab --- /dev/null +++ b/tests/run-macros/annot-mod-class-add-lazy-val/Macro_1.scala @@ -0,0 +1,25 @@ +import scala.annotation.{experimental, MacroAnnotation} +import scala.quoted._ +import scala.collection.mutable + +@experimental +class addMemoToString(msg: String) extends MacroAnnotation: + def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = + import quotes.reflect._ + tree match + case ClassDef(name, ctr, parents, self, body) => + val cls = tree.symbol + val stringLazyValSym = Symbol.newVal(cls, Symbol.freshName("string"), TypeRepr.of[String], Flags.Lazy | Flags.Private, Symbol.noSymbol) + + val toStringMethType = Symbol.requiredMethod("java.lang.Object.toString").info + val toStringOverrideSym = Symbol.newMethod(cls, "toString", toStringMethType, Flags.Override, Symbol.noSymbol) + + val stringLazyValDef = ValDef(stringLazyValSym, Some(Literal(StringConstant(msg)))) + val toStringDef = DefDef(toStringOverrideSym, _ => Some(Ref(stringLazyValSym))) + + val newClassDef = ClassDef.copy(tree)(name, ctr, parents, self, stringLazyValDef :: toStringDef :: body) + List(newClassDef) + + case _ => + report.error("Annotation only supports `class`") + List(tree) diff --git a/tests/run-macros/annot-mod-class-add-lazy-val/Test_2.scala b/tests/run-macros/annot-mod-class-add-lazy-val/Test_2.scala new file mode 100644 index 000000000000..657b40b42fc8 --- /dev/null +++ b/tests/run-macros/annot-mod-class-add-lazy-val/Test_2.scala @@ -0,0 +1,14 @@ +@addMemoToString("This is Foo") +class Foo + //> private lazy val string$macro$1: String = "This is Foo" + //> def toString(): String =string$macro$1 + +@addMemoToString("This is Foo object") +object Foo + //> private lazy val string$macro$2: String = "This is Foo object" + //> def toString(): String =string$macro$2 + +@main def Test(): Unit = + val foo = new Foo + assert(foo.toString() == "This is Foo", foo) + assert(Foo.toString() == "This is Foo object", Foo) diff --git a/tests/run-macros/annot-mod-class-add-local-class/Macro_1.scala b/tests/run-macros/annot-mod-class-add-local-class/Macro_1.scala new file mode 100644 index 000000000000..e9a1baec44bd --- /dev/null +++ b/tests/run-macros/annot-mod-class-add-local-class/Macro_1.scala @@ -0,0 +1,34 @@ +import scala.annotation.{experimental, MacroAnnotation} +import scala.quoted._ +import scala.collection.mutable + +@experimental +class addInnerClass extends MacroAnnotation: + def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = + import quotes.reflect._ + tree match + case ClassDef(name, ctr, parents, self, body) => + val cls = tree.symbol + + val toStringMethType = Symbol.requiredMethod("java.lang.Object.toString").info + val toStringOverrideSym = Symbol.newMethod(cls, "toString", toStringMethType, Flags.Override, Symbol.noSymbol) + + def showClassDecls(showCls: Symbol): List[Symbol] = + List(Symbol.newMethod(showCls, "showMe", MethodType(List("x"))(_ => List(cls.typeRef), _ => TypeRepr.of[String]))) + val parents = List(TypeTree.of[Object]) + val showClassSym = Symbol.newClass(toStringOverrideSym, "Show", parents.map(_.tpe), showClassDecls, None) + val showMeSym = showClassSym.declaredMethod("showMe").head + + val newShow = Apply(Select(New(TypeIdent(showClassSym)), showClassSym.primaryConstructor), Nil) + val newShowCallShowMe = Apply(Select(newShow, showMeSym), List(This(cls))) + + val showMeDef = DefDef(showMeSym, argss => Some('{ "showMe: " + ${argss.head.head.asExpr}.getClass }.asTerm)) + val showClass = ClassDef(showClassSym, parents, body = List(showMeDef)) + val toStringDef = DefDef(toStringOverrideSym, _ => Some(Block(List(showClass), newShowCallShowMe))) + + val newClassDef = ClassDef.copy(tree)(name, ctr, parents, self, toStringDef :: body) + List(newClassDef) + + case _ => + report.error("Annotation only supports `class`") + List(tree) diff --git a/tests/run-macros/annot-mod-class-add-local-class/Test_2.scala b/tests/run-macros/annot-mod-class-add-local-class/Test_2.scala new file mode 100644 index 000000000000..60aa28ffaf2a --- /dev/null +++ b/tests/run-macros/annot-mod-class-add-local-class/Test_2.scala @@ -0,0 +1,18 @@ +@addInnerClass +class Foo + //> def toString(): String = + //> class Show: + //> def showMe(x: Foo): String = "showMe: " + x.getClass + //> (new Show).showMe(this) + +@addInnerClass +object Bar + //> def toString(): String = + //> class Show: + //> def showMe(x: Foo): String = "showMe: " + x.getClass + //> (new Show).showMe(this) + +@main def Test(): Unit = + val foo = new Foo + assert(foo.toString() == "showMe: class Foo", foo) + assert(Bar.toString() == "showMe: class Bar$", Bar) diff --git a/tests/run-macros/annot-mod-class-add-val/Macro_1.scala b/tests/run-macros/annot-mod-class-add-val/Macro_1.scala new file mode 100644 index 000000000000..34b7cd879abe --- /dev/null +++ b/tests/run-macros/annot-mod-class-add-val/Macro_1.scala @@ -0,0 +1,25 @@ +import scala.annotation.{experimental, MacroAnnotation} +import scala.quoted._ +import scala.collection.mutable + +@experimental +class addMemoToString(msg: String) extends MacroAnnotation: + def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = + import quotes.reflect._ + tree match + case ClassDef(name, ctr, parents, self, body) => + val cls = tree.symbol + val stringValSym = Symbol.newVal(cls, Symbol.freshName("string"), TypeRepr.of[String], Flags.Private, Symbol.noSymbol) + + val toStringMethType = Symbol.requiredMethod("java.lang.Object.toString").info + val toStringOverrideSym = Symbol.newMethod(cls, "toString", toStringMethType, Flags.Override, Symbol.noSymbol) + + val stringValDef = ValDef(stringValSym, Some(Literal(StringConstant(msg)))) + val toStringDef = DefDef(toStringOverrideSym, _ => Some(Ref(stringValSym))) + + val newClassDef = ClassDef.copy(tree)(name, ctr, parents, self, stringValDef :: toStringDef :: body) + List(newClassDef) + + case _ => + report.error("Annotation only supports `class`") + List(tree) diff --git a/tests/run-macros/annot-mod-class-add-val/Test_2.scala b/tests/run-macros/annot-mod-class-add-val/Test_2.scala new file mode 100644 index 000000000000..40816f5c8c84 --- /dev/null +++ b/tests/run-macros/annot-mod-class-add-val/Test_2.scala @@ -0,0 +1,14 @@ +@addMemoToString("This is Foo") +class Foo + //> private val string$macro$1: String = "This is Foo" + //> def toString(): String = string$macro$1 + +@addMemoToString("This is Foo object") +object Foo + //> private val string$macro$2: String = "This is Foo object" + //> def toString(): String = string$macro$2 + +@main def Test(): Unit = + val foo = new Foo + assert(foo.toString() == "This is Foo", foo) + assert(Foo.toString() == "This is Foo object", Foo) diff --git a/tests/run-macros/annot-mod-class-add-var/Macro_1.scala b/tests/run-macros/annot-mod-class-add-var/Macro_1.scala new file mode 100644 index 000000000000..d1cc26102ff3 --- /dev/null +++ b/tests/run-macros/annot-mod-class-add-var/Macro_1.scala @@ -0,0 +1,32 @@ +import scala.annotation.{experimental, MacroAnnotation} +import scala.quoted._ +import scala.collection.mutable + +@experimental +class addCountToString(msg: String) extends MacroAnnotation: + def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = + import quotes.reflect._ + tree match + case ClassDef(name, ctr, parents, self, body) => + val cls = tree.symbol + val countVarSym = Symbol.newVal(cls, Symbol.freshName("count"), TypeRepr.of[Int], Flags.Mutable | Flags.Private, Symbol.noSymbol) + + val toStringMethType = Symbol.requiredMethod("java.lang.Object.toString").info + val toStringOverrideSym = Symbol.newMethod(cls, "toString", toStringMethType, Flags.Override, Symbol.noSymbol) + + val countRef = Ref(countVarSym) + val countRefExpr = countRef.asExprOf[Int] + val countVarDef = ValDef(countVarSym, Some(Literal(IntConstant(0)))) + val toStringDef = DefDef(toStringOverrideSym, _ => Some( + Block( + Assign(countRef, '{ $countRefExpr + 1 }.asTerm) :: Nil, + '{ ${Expr(msg)} + $countRefExpr }.asTerm + ) + )) + + val newClassDef = ClassDef.copy(tree)(name, ctr, parents, self, countVarDef :: toStringDef :: body) + List(newClassDef) + + case _ => + report.error("Annotation only supports `class`") + List(tree) diff --git a/tests/run-macros/annot-mod-class-add-var/Test_2.scala b/tests/run-macros/annot-mod-class-add-var/Test_2.scala new file mode 100644 index 000000000000..5058e826b863 --- /dev/null +++ b/tests/run-macros/annot-mod-class-add-var/Test_2.scala @@ -0,0 +1,26 @@ +@addCountToString("This is Foo: ") +class Foo: + //> private var count$macro$1: Int = 0 + //> def toString(): String = + //> count$macro$1 = count$macro$1 + 1 + //> "This is Foo" + count$macro$1 + + var countA: Int = 0 + def toStringA(): String = + countA = countA + 1 + "This is Foo" + countA + + +@addCountToString("This is Foo object: ") +object Foo + //> private var count$macro$2: Int = 0 + //> def toString(): String = + //> count$macro$2 = count$macro$2 + 1 + //> "This is Foo object: " + count$macro$2 + +@main def Test(): Unit = + val foo = new Foo + assert(foo.toString() == "This is Foo: 1", foo) + assert(foo.toString() == "This is Foo: 2", foo) + assert(Foo.toString() == "This is Foo object: 1", Foo) + assert(Foo.toString() == "This is Foo object: 2", Foo) diff --git a/tests/run-macros/annot-mod-class-data/Macro_1.scala b/tests/run-macros/annot-mod-class-data/Macro_1.scala new file mode 100644 index 000000000000..05f63165e1b6 --- /dev/null +++ b/tests/run-macros/annot-mod-class-data/Macro_1.scala @@ -0,0 +1,98 @@ +import scala.annotation.{experimental, MacroAnnotation} +import scala.quoted.* + +@experimental +class data extends MacroAnnotation: + def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = + import quotes.reflect.* + tree match + case ClassDef(className, ctr, parents, self, body) => + val cls = tree.symbol + + val constructorParameters = ctr.paramss.collect { case clause: TermParamClause => clause } + if constructorParameters.size != 1 || constructorParameters.head.params.isEmpty then + report.errorAndAbort("@data class must have a single argument list with at least one argument", ctr.pos) + def checkNotOverridden(sym: Symbol): Unit = + if sym.overridingSymbol(cls).exists then + report.error(s"Cannot override ${sym.name} in a @data class") + + val fields = body.collect { + case vdef: ValDef if vdef.symbol.flags.is(Flags.ParamAccessor) => + Select(This(cls), vdef.symbol).asExpr + } + + val toStringSym = Symbol.requiredMethod("java.lang.Object.toString") + checkNotOverridden(toStringSym) + val toStringOverrideSym = Symbol.newMethod(cls, "toString", toStringSym.info, Flags.Override, Symbol.noSymbol) + val toStringDef = DefDef(toStringOverrideSym, _ => + given Quotes = toStringOverrideSym.asQuotes + Some(toStringExpr(className, fields).asTerm) + + ) + + val hashCodeSym = Symbol.requiredMethod("java.lang.Object.hashCode") + checkNotOverridden(hashCodeSym) + val hashCodeOverrideSym = Symbol.newMethod(cls, "hashCode", hashCodeSym.info, Flags.Override, Symbol.noSymbol) + val hashCodeOverrideDef = DefDef(hashCodeOverrideSym, _ => + given Quotes = hashCodeOverrideSym.asQuotes + Some(hashCodeExpr(className, fields).asTerm) + ) + + val equalsSym = Symbol.requiredMethod("java.lang.Object.equals") + checkNotOverridden(equalsSym) + val equalsOverrideSym = Symbol.newMethod(cls, "equals", equalsSym.info, Flags.Override, Symbol.noSymbol) + def equalsOverrideDefBody(argss: List[List[Tree]]): Option[Term] = + given Quotes = equalsOverrideSym.asQuotes + cls.typeRef.asType match + case '[c] => + Some(equalsExpr[c](argss.head.head.asExpr, fields).asTerm) + val equalsOverrideDef = DefDef(equalsOverrideSym, equalsOverrideDefBody) + + val newBody = toStringDef :: hashCodeOverrideDef :: equalsOverrideDef :: body + List(ClassDef.copy(tree)(className, ctr, parents, self, newBody)) + case _ => + report.error("Annotation only supports `class`") + List(tree) + + private def toStringExpr(className: String, thisFields: List[Expr[Any]])(using Quotes): Expr[String] = + val fieldsSeq = Expr.ofSeq(thisFields) + val prefix = Expr(className + "(") + '{ $fieldsSeq.mkString($prefix, ", ", ")") } + + private def hashCodeExpr(className: String, thisFields: List[Expr[Any]])(using Quotes): Expr[Int] = + '{ + var acc: Int = ${ Expr(scala.runtime.Statics.mix(-889275714, className.hashCode)) } + ${ + Expr.block( + thisFields.map { + case '{ $field: Boolean } => '{ if $field then 1231 else 1237 } + case '{ $field: Byte } => '{ $field.toInt } + case '{ $field: Char } => '{ $field.toInt } + case '{ $field: Short } => '{ $field.toInt } + case '{ $field: Int } => field + case '{ $field: Long } => '{ scala.runtime.Statics.longHash($field) } + case '{ $field: Double } => '{ scala.runtime.Statics.doubleHash($field) } + case '{ $field: Float } => '{ scala.runtime.Statics.floatHash($field) } + case '{ $field: Null } => '{ 0 } + case '{ $field: Unit } => '{ 0 } + case field => '{ scala.runtime.Statics.anyHash($field) } + }.map(hash => '{ acc = scala.runtime.Statics.mix(acc, $hash) }), + '{ scala.runtime.Statics.finalizeHash(acc, ${Expr(thisFields.size)}) } + ) + } + } + + private def equalsExpr[T: Type](that: Expr[Any], thisFields: List[Expr[Any]])(using Quotes): Expr[Boolean] = + '{ + $that match + case that: T @unchecked => + ${ + val thatFields: List[Expr[Any]] = + import quotes.reflect.* + thisFields.map(field => Select('{that}.asTerm, field.asTerm.symbol).asExpr) + thisFields.zip(thatFields) + .map { case (thisField, thatField) => '{ $thisField == $thatField } } + .reduce { case (pred1, pred2) => '{ $pred1 && $pred2 } } + } + case _ => false + } diff --git a/tests/run-macros/annot-mod-class-data/Test_2.scala b/tests/run-macros/annot-mod-class-data/Test_2.scala new file mode 100644 index 000000000000..23e5191a3aeb --- /dev/null +++ b/tests/run-macros/annot-mod-class-data/Test_2.scala @@ -0,0 +1,31 @@ +@data class Foo(val a: String, val b: Int) + //> override def toString(): String = Seq(this.a, this.b).mkString("Foo(", ", ", ")") + //> override def equals(that: Any): Boolean = + //> that match + //> case that: Foo => this.a.==(that.a).&&(this.b.==(that.b)) + //> case _ => false + //> override def hashCode(): Int = + //> var acc = -1566937476 // scala.runtime.Statics.mix(-889275714, "Foo".hashCode) + //> acc = scala.runtime.Statics.mix(acc, scala.runtime.Statics.anyHash(a)) + //> acc = scala.runtime.Statics.mix(acc, b) + //> scala.runtime.Statics.finalizeHash(acc, 2) + +@data class Bar(val a: String) + //> override def toString(): String = Seq(this.a).mkString("Foo(", ", ", ")") + //> override def equals(that: Any): Boolean = + //> that match + //> case that: Bar => this.a.==(that.a) + //> case _ => false + //> override def hashCode(): Int = + //> var acc = 1555242735 // scala.runtime.Statics.mix(-889275714, "Bar".hashCode) + //> acc = scala.runtime.Statics.mix(acc, scala.runtime.Statics.anyHash(a)) + //> scala.runtime.Statics.finalizeHash(acc, 1) + +@main def Test(): Unit = + assert(Foo("abc", 2).toString() == "Foo(abc, 2)", Foo("abc", 2)) + assert(Foo("abc", 2) == Foo("abc", 2)) + assert(Foo("abc", 2).hashCode() == Foo("abc", 2).hashCode()) + + assert(Bar("abc").toString() == "Bar(abc)", Bar("abc")) + assert(Bar("abc") == Bar("abc")) + assert(Bar("abc").hashCode() == Bar("abc").hashCode()) diff --git a/tests/run-macros/annot-mod-class-equals/Macro_1.scala b/tests/run-macros/annot-mod-class-equals/Macro_1.scala new file mode 100644 index 000000000000..3527ec3289c6 --- /dev/null +++ b/tests/run-macros/annot-mod-class-equals/Macro_1.scala @@ -0,0 +1,84 @@ +import scala.annotation.{experimental, MacroAnnotation} +import scala.quoted.* + +@experimental +class equals extends MacroAnnotation: + def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = + import quotes.reflect.* + tree match + case ClassDef(className, ctr, parents, self, body) => + val cls = tree.symbol + + val constructorParameters = ctr.paramss.collect { case clause: TermParamClause => clause } + if constructorParameters.size != 1 || constructorParameters.head.params.isEmpty then + report.errorAndAbort("@equals class must have a single argument list with at least one argument", ctr.pos) + def checkNotOverridden(sym: Symbol): Unit = + if sym.overridingSymbol(cls).exists then + report.error(s"Cannot override ${sym.name} in a @equals class") + + val fields = body.collect { + case vdef: ValDef if vdef.symbol.flags.is(Flags.ParamAccessor) => + Select(This(cls), vdef.symbol).asExpr + } + + val equalsSym = Symbol.requiredMethod("java.lang.Object.equals") + checkNotOverridden(equalsSym) + val equalsOverrideSym = Symbol.newMethod(cls, "equals", equalsSym.info, Flags.Override, Symbol.noSymbol) + def equalsOverrideDefBody(argss: List[List[Tree]]): Option[Term] = + given Quotes = equalsOverrideSym.asQuotes + cls.typeRef.asType match + case '[c] => + Some(equalsExpr[c](argss.head.head.asExpr, fields).asTerm) + val equalsOverrideDef = DefDef(equalsOverrideSym, equalsOverrideDefBody) + + val hashSym = Symbol.newVal(cls, Symbol.freshName("hash"), TypeRepr.of[Int], Flags.Private | Flags.Lazy, Symbol.noSymbol) + val hashVal = ValDef(hashSym, Some(hashCodeExpr(className, fields)(using hashSym.asQuotes).asTerm)) + + val hashCodeSym = Symbol.requiredMethod("java.lang.Object.hashCode") + checkNotOverridden(hashCodeSym) + val hashCodeOverrideSym = Symbol.newMethod(cls, "hashCode", hashCodeSym.info, Flags.Override, Symbol.noSymbol) + val hashCodeOverrideDef = DefDef(hashCodeOverrideSym, _ => Some(Ref(hashSym))) + + val newBody = equalsOverrideDef :: hashVal :: hashCodeOverrideDef :: body + List(ClassDef.copy(tree)(className, ctr, parents, self, newBody)) + case _ => + report.error("Annotation only supports `class`") + List(tree) + + private def equalsExpr[T: Type](that: Expr[Any], thisFields: List[Expr[Any]])(using Quotes): Expr[Boolean] = + '{ + $that match + case that: T @unchecked => + ${ + val thatFields: List[Expr[Any]] = + import quotes.reflect.* + thisFields.map(field => Select('{that}.asTerm, field.asTerm.symbol).asExpr) + thisFields.zip(thatFields) + .map { case (thisField, thatField) => '{ $thisField == $thatField } } + .reduce { case (pred1, pred2) => '{ $pred1 && $pred2 } } + } + case _ => false + } + + private def hashCodeExpr(className: String, thisFields: List[Expr[Any]])(using Quotes): Expr[Int] = + '{ + var acc: Int = ${ Expr(scala.runtime.Statics.mix(-889275714, className.hashCode)) } + ${ + Expr.block( + thisFields.map { + case '{ $field: Boolean } => '{ if $field then 1231 else 1237 } + case '{ $field: Byte } => '{ $field.toInt } + case '{ $field: Char } => '{ $field.toInt } + case '{ $field: Short } => '{ $field.toInt } + case '{ $field: Int } => field + case '{ $field: Long } => '{ scala.runtime.Statics.longHash($field) } + case '{ $field: Double } => '{ scala.runtime.Statics.doubleHash($field) } + case '{ $field: Float } => '{ scala.runtime.Statics.floatHash($field) } + case '{ $field: Null } => '{ 0 } + case '{ $field: Unit } => '{ 0 } + case field => '{ scala.runtime.Statics.anyHash($field) } + }.map(hash => '{ acc = scala.runtime.Statics.mix(acc, $hash) }), + '{ scala.runtime.Statics.finalizeHash(acc, ${Expr(thisFields.size)}) } + ) + } + } diff --git a/tests/run-macros/annot-mod-class-equals/Test_2.scala b/tests/run-macros/annot-mod-class-equals/Test_2.scala new file mode 100644 index 000000000000..a4b3b406c4ce --- /dev/null +++ b/tests/run-macros/annot-mod-class-equals/Test_2.scala @@ -0,0 +1,29 @@ +@equals class Foo(val a: String, val b: Int) + //> override def equals(that: Any): Boolean = + //> that match + //> case that: Foo => this.a.==(that.a).&&(this.b.==(that.b)) + //> case _ => false + //> private lazy val hash$macro$1: Int = + //> var acc = -1566937476 // scala.runtime.Statics.mix(-889275714, "Foo".hashCode) + //> acc = scala.runtime.Statics.mix(acc, scala.runtime.Statics.anyHash(a)) + //> acc = scala.runtime.Statics.mix(acc, b) + //> scala.runtime.Statics.finalizeHash(acc, 2) + //> override def hashCode(): Int = hash$macro$1 + +@equals class Bar(val a: String) + //> override def equals(that: Any): Boolean = + //> that match + //> case that: Bar => this.a.==(that.a) + //> case _ => false + //> private lazy val hash$macro$2: Int = + //> var acc = 1555242735 // scala.runtime.Statics.mix(-889275714, "Bar".hashCode) + //> acc = scala.runtime.Statics.mix(acc, scala.runtime.Statics.anyHash(a)) + //> scala.runtime.Statics.finalizeHash(acc, 1) + //> override def hashCode(): Int = hash$macro$2 + +@main def Test(): Unit = + assert(Foo("abc", 2) == Foo("abc", 2)) + assert(Foo("abc", 2).hashCode() == Foo("abc", 2).hashCode()) + + assert(Bar("abc") == Bar("abc")) + assert(Bar("abc").hashCode() == Bar("abc").hashCode()) diff --git a/tests/run-macros/annot-mod-class-mod-def/Macro_1.scala b/tests/run-macros/annot-mod-class-mod-def/Macro_1.scala new file mode 100644 index 000000000000..98fc91f31322 --- /dev/null +++ b/tests/run-macros/annot-mod-class-mod-def/Macro_1.scala @@ -0,0 +1,25 @@ +import scala.annotation.{experimental, MacroAnnotation} +import scala.quoted._ +import scala.collection.mutable + +@experimental +class modToString(msg: String) extends MacroAnnotation: + def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = + import quotes.reflect._ + tree match + case ClassDef(name, ctr, parents, self, body) => + val cls = tree.symbol + val toStringSym = cls.methodMember("toString").head + + val newBody = body.span(_.symbol != toStringSym) match + case (before, toStringDef :: after) => + val newToStringDef = DefDef(toStringDef.symbol, _ => Some(Literal(StringConstant(msg)))) + before ::: newToStringDef :: after + case _ => + report.error("toString was not defined") + body + + List(ClassDef.copy(tree)(name, ctr, parents, self, newBody)) + case _ => + report.error("Annotation only supports `class`") + List(tree) diff --git a/tests/run-macros/annot-mod-class-mod-def/Test_2.scala b/tests/run-macros/annot-mod-class-mod-def/Test_2.scala new file mode 100644 index 000000000000..7e5da4d7b31d --- /dev/null +++ b/tests/run-macros/annot-mod-class-mod-def/Test_2.scala @@ -0,0 +1,12 @@ +@modToString("This is Foo") +class Foo: + override def toString(): String = "?" //> override def toString(): String = "This is Foo" + +@modToString("This is Foo object") +object Foo: + override def toString(): String = "?" //> override def toString(): String = "This is Foo object" + +@main def Test(): Unit = + val foo = new Foo + assert(foo.toString() == "This is Foo", foo) + assert(Foo.toString() == "This is Foo object", Foo) diff --git a/tests/run-macros/annot-mod-class-mod-val/Macro_1.scala b/tests/run-macros/annot-mod-class-mod-val/Macro_1.scala new file mode 100644 index 000000000000..036703a8a374 --- /dev/null +++ b/tests/run-macros/annot-mod-class-mod-val/Macro_1.scala @@ -0,0 +1,25 @@ +import scala.annotation.{experimental, MacroAnnotation} +import scala.quoted._ +import scala.collection.mutable + +@experimental +class setValue(field: String, value: String) extends MacroAnnotation: + def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = + import quotes.reflect._ + tree match + case ClassDef(name, ctr, parents, self, body) => + val cls = tree.symbol + val valSym = cls.fieldMember(field) + + val newBody = body.span(_.symbol != valSym) match + case (before, valDef :: after) => + val newValDef = ValDef(valSym, Some(Literal(StringConstant(value)))) + before ::: newValDef :: after + case _ => + report.error(s"`val $field` was not defined") + body + + List(ClassDef.copy(tree)(name, ctr, parents, self, newBody)) + case _ => + report.error("Annotation only supports `class`") + List(tree) diff --git a/tests/run-macros/annot-mod-class-mod-val/Test_2.scala b/tests/run-macros/annot-mod-class-mod-val/Test_2.scala new file mode 100644 index 000000000000..d75138f0dc41 --- /dev/null +++ b/tests/run-macros/annot-mod-class-mod-val/Test_2.scala @@ -0,0 +1,25 @@ +@setValue("valDef", "a") +@setValue("varDef", "b") +@setValue("lazyVarDef", "c") +class Foo: + val valDef: String = "?" //> val valDef: String = "a" + var varDef: String = "?" //> var varDef: String = "b" + lazy val lazyVarDef: String = "?" //> lazy val lazyVarDef: String = "c" + +@setValue("valDef", "a") +@setValue("varDef", "b") +@setValue("lazyVarDef", "c") +object Foo: + val valDef: String = "?" //> val valDef: String = "a" + var varDef: String = "?" //> var varDef: String = "b" + lazy val lazyVarDef: String = "?" //> lazy val lazyVarDef: String = "c" + +@main def Test(): Unit = + val foo = new Foo + assert(foo.valDef == "a", foo.valDef) + assert(foo.varDef == "b", foo.varDef) + assert(foo.lazyVarDef == "c", foo.lazyVarDef) + + assert(Foo.valDef == "a", Foo.valDef) + assert(Foo.varDef == "b", Foo.varDef) + assert(Foo.lazyVarDef == "c", Foo.lazyVarDef) diff --git a/tests/run-macros/annot-mod-class-override-def/Macro_1.scala b/tests/run-macros/annot-mod-class-override-def/Macro_1.scala new file mode 100644 index 000000000000..7d591a39b091 --- /dev/null +++ b/tests/run-macros/annot-mod-class-override-def/Macro_1.scala @@ -0,0 +1,22 @@ +import scala.annotation.{experimental, MacroAnnotation} +import scala.quoted._ +import scala.collection.mutable + +@experimental +class genToString(msg: String) extends MacroAnnotation: + def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = + import quotes.reflect._ + tree match + case ClassDef(name, ctr, parents, self, body) => + val cls = tree.symbol + val toStringSym = Symbol.requiredMethod("java.lang.Object.toString") + + val toStringOverrideSym = Symbol.newMethod(cls, "toString", toStringSym.info, Flags.Override, Symbol.noSymbol) + + val toStringDef = DefDef(toStringOverrideSym, _ => Some(Literal(StringConstant(msg)))) + + val newClassDef = ClassDef.copy(tree)(name, ctr, parents, self, toStringDef :: body) + List(newClassDef) + case _ => + report.error("Annotation only supports `class`") + List(tree) diff --git a/tests/run-macros/annot-mod-class-override-def/Test_2.scala b/tests/run-macros/annot-mod-class-override-def/Test_2.scala new file mode 100644 index 000000000000..0fca75ba9d6a --- /dev/null +++ b/tests/run-macros/annot-mod-class-override-def/Test_2.scala @@ -0,0 +1,12 @@ +@genToString("This is Foo") +class Foo + //> override def toString(): String = "This is Foo" + +@genToString("This is Foo object") +object Foo + //> override def toString(): String = "This is Foo" + +@main def Test(): Unit = + val foo = new Foo + assert(foo.toString() == "This is Foo", foo) + assert(Foo.toString() == "This is Foo object", Foo) diff --git a/tests/run-macros/annot-mod-class-override-val/Macro_1.scala b/tests/run-macros/annot-mod-class-override-val/Macro_1.scala new file mode 100644 index 000000000000..c64e06686fa0 --- /dev/null +++ b/tests/run-macros/annot-mod-class-override-val/Macro_1.scala @@ -0,0 +1,21 @@ +import scala.annotation.{experimental, MacroAnnotation} +import scala.quoted._ +import scala.collection.mutable + +@experimental +class overrideField(field: String, value: String) extends MacroAnnotation: + def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = + import quotes.reflect._ + tree match + case ClassDef(name, ctr, parents, self, body) => + val cls = tree.symbol + + val overrideSym = Symbol.newVal(cls, field, TypeRepr.of[String], Flags.Override, Symbol.noSymbol) + + val valDef = ValDef(overrideSym, Some(Literal(StringConstant(value)))) + + val newClassDef = ClassDef.copy(tree)(name, ctr, parents, self, valDef :: body) + List(newClassDef) + case _ => + report.error("Annotation only supports `class`") + List(tree) diff --git a/tests/run-macros/annot-mod-class-override-val/Test_2.scala b/tests/run-macros/annot-mod-class-override-val/Test_2.scala new file mode 100644 index 000000000000..52d56723980d --- /dev/null +++ b/tests/run-macros/annot-mod-class-override-val/Test_2.scala @@ -0,0 +1,23 @@ +class Foo: + val val1: String = "?" + def def1: String = "?" + +@overrideField("val1", "a") +@overrideField("def1", "b") +class Bar extends Foo + //> val val1: String = "a" + //> def def1: String = "b" + +@overrideField("val1", "a") +@overrideField("def1", "b") +object Foo extends Foo + //> val val1: String = "a" + //> def def1: String = "b" + +@main def Test(): Unit = + val foo = new Bar + assert(foo.val1 == "a", foo.val1) + assert(foo.def1 == "b", foo.def1) + + assert(Foo.val1 == "a", Foo.val1) + assert(Foo.def1 == "b", Foo.def1) diff --git a/tests/run-macros/annot-mod-class-unused-new-sym/Macro_1.scala b/tests/run-macros/annot-mod-class-unused-new-sym/Macro_1.scala new file mode 100644 index 000000000000..0da1e4106e8e --- /dev/null +++ b/tests/run-macros/annot-mod-class-unused-new-sym/Macro_1.scala @@ -0,0 +1,18 @@ +import scala.annotation.{experimental, MacroAnnotation} +import scala.quoted._ +import scala.collection.mutable + +@experimental +class newUnusedSymbol extends MacroAnnotation: + def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = + import quotes.reflect._ + tree match + case ClassDef(name, ctr, parents, self, body) => + val cls = tree.symbol + val toStringMethType = Symbol.requiredMethod("java.lang.Object.toString").info + val toStringOverrideSym = Symbol.newMethod(cls, "toString", toStringMethType, Flags.Override, Symbol.noSymbol) + // Test that toStringOverrideSym is not accidentally entered in the class + List(tree) + case _ => + report.error("Annotation only supports `class`") + List(tree) diff --git a/tests/run-macros/annot-mod-class-unused-new-sym/Test_2.scala b/tests/run-macros/annot-mod-class-unused-new-sym/Test_2.scala new file mode 100644 index 000000000000..dd93d0df2917 --- /dev/null +++ b/tests/run-macros/annot-mod-class-unused-new-sym/Test_2.scala @@ -0,0 +1,8 @@ +@newUnusedSymbol +class Foo + +@newUnusedSymbol +object Foo + +@main def Test(): Unit = + val foo = new Foo