@@ -3,50 +3,61 @@ package steps.annotation
33import scala .annotation .{experimental , MacroAnnotation }
44import scala .quoted .*
55
6-
7- /**
8- * A macro annotation that automatically generates a custom `toString` method for a class.
9- *
10- * The `@toString` annotation can be applied to a class, object, or trait. When applied, it overrides the `toString`
11- * method to include the class name and the values of all fields marked as `ParamAccessor`.
12- *
13- * If the class already defines or overrides the `toString` method, the annotation will emit a warning indicating that
14- * the annotation is not necessary. The existing `toString` method will remain unchanged.
15- *
16- * Example usage:
17- * {{{
18- * @toString
19- * class MyClass(val a: Int, val b: String)
20- *
21- * val instance = MyClass(1, "hello")
22- * println(instance.toString) // Output: MyClass(1, hello)
23- * }}}
24- *
25- * The generated `toString` method produces output in the format: `ClassName(field1, field2, ...)`.
26- *
27- * @note This annotation requires Scala's `experimental` flag to be enabled, or it can be used within a scope
28- * marked as experimental (i.e., using `import scala.annotation.experimental`). This is necessary because it
29- * relies on experimental macro annotation features.
30- */
6+ /** A macro annotation that automatically generates a custom `toString` method
7+ * for a class.
8+ *
9+ * The `@toString` annotation can be applied to a class, object, or trait. When
10+ * applied, it overrides the `toString` method to include the class name and
11+ * the values of all fields marked as `ParamAccessor`.
12+ *
13+ * If the class already defines or overrides the `toString` method, the
14+ * annotation will emit a warning indicating that the annotation is not
15+ * necessary. The existing `toString` method will remain unchanged.
16+ *
17+ * Example usage:
18+ * {{{
19+ * @toString
20+ * class MyClass(val a: Int, val b: String)
21+ *
22+ * val instance = MyClass(1, "hello")
23+ * println(instance.toString) // Output: MyClass(1, hello)
24+ * }}}
25+ *
26+ * The generated `toString` method produces output in the format:
27+ * `ClassName(field1, field2, ...)`.
28+ *
29+ * @note
30+ * This annotation requires Scala's `experimental` flag to be enabled, or it
31+ * can be used within a scope marked as experimental (i.e., using `import
32+ * scala.annotation.experimental`). This is necessary because it relies on
33+ * experimental macro annotation features.
34+ */
3135@ experimental
3236final class toString extends MacroAnnotation :
3337
34- /**
35- * Transforms the annotated class to add a custom `toString` method.
36- *
37- * If the class already overrides `toString`, a warning is emitted and no changes are made.
38- * Otherwise, this annotation adds a new `toString` method that returns a string representation of
39- * the class name and its fields.
40- *
41- * @param tree The abstract syntax tree (AST) of the annotated class, object, or trait.
42- * @return The transformed class definition, with the generated `toString` method if applicable.
43- */
44- override def transform (using Quotes )(tree : quotes.reflect.Definition ): List [quotes.reflect.Definition ] =
38+ /** Transforms the annotated class to add a custom `toString` method.
39+ *
40+ * If the class already overrides `toString`, a warning is emitted and no
41+ * changes are made. Otherwise, this annotation adds a new `toString` method
42+ * that returns a string representation of the class name and its fields.
43+ *
44+ * @param tree
45+ * The abstract syntax tree (AST) of the annotated class, object, or trait.
46+ * @return
47+ * The transformed class definition, with the generated `toString` method
48+ * if applicable.
49+ */
50+ override def transform (using quotes : Quotes )(
51+ tree : quotes.reflect.Definition ,
52+ _companion : Option [quotes.reflect.Definition ]
53+ ): List [quotes.reflect.Definition ] =
4554 import quotes .reflect .*
4655 val toStringSym = Symbol .requiredMethod(" java.lang.Object.toString" )
4756 tree match
4857 case _ : ClassDef if toStringSym.overridingSymbol(tree.symbol).exists =>
49- report.warning(s " @toString is not necessary since toString is defined in ${tree.symbol}" )
58+ report.warning(
59+ s " @toString is not necessary since toString is defined in ${tree.symbol}"
60+ )
5061 List (tree)
5162 case cls : ClassDef if cls.symbol.flags.is(Flags .Trait ) =>
5263 report.error(s " @toString is not supported on traits " )
@@ -62,31 +73,44 @@ final class toString extends MacroAnnotation:
6273 Select (This (cls), vdef.symbol).asExpr
6374 }
6475
65- val toStringOverrideSym = Symbol .newMethod(cls, toStringSym.name, toStringSym.info, Flags .Override , Symbol .noSymbol)
76+ val toStringOverrideSym = Symbol .newMethod(
77+ cls,
78+ toStringSym.name,
79+ toStringSym.info,
80+ Flags .Override ,
81+ Symbol .noSymbol
82+ )
6683
6784 def toStringOverrideDefBody (argss : List [List [Tree ]]): Option [Term ] =
6885 given Quotes = toStringOverrideSym.asQuotes
6986 Some (toStringExpr(className, fields).asTerm)
7087
7188 val toStringDef = DefDef (toStringOverrideSym, toStringOverrideDefBody)
72- List (ClassDef .copy(tree)(className, ctr, parents, self, toStringDef :: body))
89+ List (
90+ ClassDef
91+ .copy(tree)(className, ctr, parents, self, toStringDef :: body)
92+ )
7393 case _ =>
7494 report.errorAndAbort(" @toString is only supported on class" )
7595 end transform
7696
77- /**
78- * Helper method to create the string representation of the class.
79- *
80- * Constructs a string in the format: `ClassName(field1, field2, ...)`.
81- *
82- * @param className The name of the class.
83- * @param thisFields The list of expressions representing the class fields.
84- * @return A quoted expression representing the final string.
85- */
86- private def toStringExpr (className : String , thisFields : List [Expr [Any ]])(using Quotes ): Expr [String ] =
97+ /** Helper method to create the string representation of the class.
98+ *
99+ * Constructs a string in the format: `ClassName(field1, field2, ...)`.
100+ *
101+ * @param className
102+ * The name of the class.
103+ * @param thisFields
104+ * The list of expressions representing the class fields.
105+ * @return
106+ * A quoted expression representing the final string.
107+ */
108+ private def toStringExpr (className : String , thisFields : List [Expr [Any ]])(using
109+ Quotes
110+ ): Expr [String ] =
87111 val fieldsSeq = Expr .ofSeq(thisFields)
88112 val prefix = Expr (className + " (" )
89113 ' { $fieldsSeq.mkString($prefix, " , " , " )" ) }
90114 end toStringExpr
91115
92- end toString
116+ end toString
0 commit comments