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/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/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index cd7201ea96aa..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 @@ -2519,6 +2516,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/annotation/MacroAnnotation.scala b/library/src/scala/annotation/MacroAnnotation.scala index 7108136cbbb8..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 @@ -34,7 +35,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 +62,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") @@ -72,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/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index 875e60ac3d67..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 */ @@ -3772,6 +3746,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] @@ -4411,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-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/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 3d6c1ba81683..422d88efc095 100644 --- a/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala +++ b/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala @@ -74,8 +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", + "scala.quoted.Quotes.reflectModule.SymbolModule.freshName", + "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/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-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 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 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 +}