Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit e4d781a

Browse files
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`
1 parent 5aa558c commit e4d781a

File tree

42 files changed

+994
-137
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+994
-137
lines changed

‎compiler/src/dotty/tools/dotc/transform/MacroAnnotations.scala‎

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import dotty.tools.dotc.config.Printers.{macroAnnot => debug}
99
import dotty.tools.dotc.core.Annotations.*
1010
import dotty.tools.dotc.core.Contexts.*
1111
import dotty.tools.dotc.core.Decorators.*
12+
import dotty.tools.dotc.core.Decorators.*
1213
import dotty.tools.dotc.core.DenotTransformers.DenotTransformer
1314
import dotty.tools.dotc.core.Flags.*
1415
import dotty.tools.dotc.core.MacroClassLoader
@@ -29,14 +30,10 @@ class MacroAnnotations(thisPhase: DenotTransformer):
2930
def expandAnnotations(tree: MemberDef)(using Context): List[DefTree] =
3031
if !hasMacroAnnotation(tree.symbol) then
3132
List(tree)
32-
else if tree.symbol.is(Module) then
33-
if tree.symbol.isClass then // error only reported on module class
34-
report.error("macro annotations are not supported on object", tree)
35-
List(tree)
36-
else if tree.symbol.isClass then
37-
report.error("macro annotations are not supported on class", tree)
33+
else if tree.symbol.is(Module) && !tree.symbol.isClass then
34+
// only class is transformed
3835
List(tree)
39-
else if tree.symbol.isType then
36+
else if tree.symbol.isType &&!tree.symbol.isClass then
4037
report.error("macro annotations are not supported on type", tree)
4138
List(tree)
4239
else
@@ -98,11 +95,13 @@ class MacroAnnotations(thisPhase: DenotTransformer):
9895
private def checkAndEnter(newTree: Tree, annotated: Symbol, annot: Annotation)(using Context) =
9996
val sym = newTree.symbol
10097
if sym.isClass then
101-
report.error("Generating classes is not supported", annot.tree)
98+
report.error(i"macro annotation returning a `class` is not yet supported. $annot tried to add $sym", annot.tree)
10299
else if sym.isType then
103-
report.error("Generating type is not supported", annot.tree)
100+
report.error(i"macro annotation cannot return a `type`. $annot tried to add $sym", annot.tree)
104101
else if sym.owner != annotated.owner then
105102
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)
103+
else if annotated.isClass && annotated.owner.is(Package) /*&& !sym.isClass*/ then
104+
report.error(i"macro annotation can not add top-level ${sym.showKind}. $annot tried to add $sym.", annot.tree)
106105
else
107106
sym.enteredAfter(thisPhase)
108107

‎library/src/scala/annotation/MacroAnnotation.scala‎

Lines changed: 112 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,12 @@ trait MacroAnnotation extends StaticAnnotation:
1818
*
1919
* All definitions in the result must have the same owner. The owner can be recovered from `tree.symbol.owner`.
2020
*
21-
* The result cannot contain `class`, `object` or `type` definition. This limitation will be relaxed in the future.
21+
* The result cannot add new `class`, `object` or `type` definition. This limitation will be relaxed in the future.
2222
*
23-
* When developing and testing a macro annotation, you must enable `-Xcheck-macros` and `-Ycheck:all`.
23+
* IMPORTANT: When developing and testing a macro annotation, you must enable `-Xcheck-macros` and `-Ycheck:all`.
2424
*
25-
* Example:
25+
* Example 1:
26+
* This example shows how to modify a `def` and add a `val` next to it using a macro annotation.
2627
* ```scala
2728
* import scala.quoted.*
2829
* import scala.collection.mutable
@@ -73,6 +74,114 @@ trait MacroAnnotation extends StaticAnnotation:
7374
* )
7475
* ```
7576
*
77+
* Example 2:
78+
* This example shows how to modify a `class` using a macro annotation.
79+
* It shows how to override inherited members and add new ones.
80+
* ```scala
81+
* import scala.annotation.{experimental, MacroAnnotation}
82+
* import scala.quoted.*
83+
*
84+
* @experimental
85+
* class equals extends MacroAnnotation:
86+
* def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] =
87+
* import quotes.reflect.*
88+
* tree match
89+
* case ClassDef(className, ctr, parents, self, body) =>
90+
* val cls = tree.symbol
91+
*
92+
* val constructorParameters = ctr.paramss.collect { case clause: TermParamClause => clause }
93+
* if constructorParameters.size != 1 || constructorParameters.head.params.isEmpty then
94+
* report.errorAndAbort("@equals class must have a single argument list with at least one argument", ctr.pos)
95+
* def checkNotOverridden(sym: Symbol): Unit =
96+
* if sym.overridingSymbol(cls).exists then
97+
* report.error(s"Cannot override ${sym.name} in a @equals class")
98+
*
99+
* val fields = body.collect {
100+
* case vdef: ValDef if vdef.symbol.flags.is(Flags.ParamAccessor) =>
101+
* Select(This(cls), vdef.symbol).asExpr
102+
* }
103+
*
104+
* val equalsSym = Symbol.requiredMethod("java.lang.Object.equals")
105+
* checkNotOverridden(equalsSym)
106+
* val equalsOverrideSym = Symbol.newMethod(cls, "equals", equalsSym.info, Flags.Override, Symbol.noSymbol)
107+
* def equalsOverrideDefBody(argss: List[List[Tree]]): Option[Term] =
108+
* given Quotes = equalsOverrideSym.asQuotes
109+
* cls.typeRef.asType match
110+
* case '[c] =>
111+
* Some(equalsExpr[c](argss.head.head.asExpr, fields).asTerm)
112+
* val equalsOverrideDef = DefDef(equalsOverrideSym, equalsOverrideDefBody)
113+
*
114+
* val hashSym = Symbol.newVal(cls, Symbol.freshName("hash"), TypeRepr.of[Int], Flags.Private | Flags.Lazy, Symbol.noSymbol)
115+
* val hashVal = ValDef(hashSym, Some(hashCodeExpr(className, fields)(using hashSym.asQuotes).asTerm))
116+
*
117+
* val hashCodeSym = Symbol.requiredMethod("java.lang.Object.hashCode")
118+
* checkNotOverridden(hashCodeSym)
119+
* val hashCodeOverrideSym = Symbol.newMethod(cls, "hashCode", hashCodeSym.info, Flags.Override, Symbol.noSymbol)
120+
* val hashCodeOverrideDef = DefDef(hashCodeOverrideSym, _ => Some(Ref(hashSym)))
121+
*
122+
* val newBody = equalsOverrideDef :: hashVal :: hashCodeOverrideDef :: body
123+
* List(ClassDef.copy(tree)(className, ctr, parents, self, newBody))
124+
* case _ =>
125+
* report.error("Annotation only supports `class`")
126+
* List(tree)
127+
*
128+
* private def equalsExpr[T: Type](that: Expr[Any], thisFields: List[Expr[Any]])(using Quotes): Expr[Boolean] =
129+
* '{
130+
* $that match
131+
* case that: T @unchecked =>
132+
* ${
133+
* val thatFields: List[Expr[Any]] =
134+
* import quotes.reflect.*
135+
* thisFields.map(field => Select('{that}.asTerm, field.asTerm.symbol).asExpr)
136+
* thisFields.zip(thatFields)
137+
* .map { case (thisField, thatField) => '{ $thisField == $thatField } }
138+
* .reduce { case (pred1, pred2) => '{ $pred1 && $pred2 } }
139+
* }
140+
* case _ => false
141+
* }
142+
*
143+
* private def hashCodeExpr(className: String, thisFields: List[Expr[Any]])(using Quotes): Expr[Int] =
144+
* '{
145+
* var acc: Int = ${ Expr(scala.runtime.Statics.mix(-889275714, className.hashCode)) }
146+
* ${
147+
* Expr.block(
148+
* thisFields.map {
149+
* case '{ $field: Boolean } => '{ if $field then 1231 else 1237 }
150+
* case '{ $field: Byte } => '{ $field.toInt }
151+
* case '{ $field: Char } => '{ $field.toInt }
152+
* case '{ $field: Short } => '{ $field.toInt }
153+
* case '{ $field: Int } => field
154+
* case '{ $field: Long } => '{ scala.runtime.Statics.longHash($field) }
155+
* case '{ $field: Double } => '{ scala.runtime.Statics.doubleHash($field) }
156+
* case '{ $field: Float } => '{ scala.runtime.Statics.floatHash($field) }
157+
* case '{ $field: Null } => '{ 0 }
158+
* case '{ $field: Unit } => '{ 0 }
159+
* case field => '{ scala.runtime.Statics.anyHash($field) }
160+
* }.map(hash => '{ acc = scala.runtime.Statics.mix(acc, $hash) }),
161+
* '{ scala.runtime.Statics.finalizeHash(acc, ${Expr(thisFields.size)}) }
162+
* )
163+
* }
164+
* }
165+
* ```
166+
* with this macro annotation a user can write
167+
* ```scala sc:nocompile
168+
* @equals class User(val name: String, val id: Int)
169+
* ```
170+
* and the macro will modify the class definition to generate the following code
171+
* ```scala
172+
* class User(val name: String, val id: Int):
173+
* override def equals(that: Any): Boolean =
174+
* that match
175+
* case that: User => this.name == that.name && this.id == that.id
176+
* case _ => false
177+
* private lazy val hash$macro1ドル: Int =
178+
* var acc = 515782504 // scala.runtime.Statics.mix(-889275714, "User".hashCode)
179+
* acc = scala.runtime.Statics.mix(acc, scala.runtime.Statics.anyHash(name))
180+
* acc = scala.runtime.Statics.mix(acc, id)
181+
* scala.runtime.Statics.finalizeHash(acc, 2)
182+
* override def hashCode(): Int = hash$macro1ドル
183+
* ```
184+
*
76185
* @param Quotes Implicit instance of Quotes used for tree reflection
77186
* @param tree Tree that will be transformed
78187
*/
Lines changed: 78 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,98 @@
11

2-
-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:18:6 ---------------------------------------------------------
3-
17 | @error
4-
18 | val vMember: Int = 1 // error
2+
-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:17:6 ---------------------------------------------------------
3+
16 |@error
4+
17 |class cGlobal // error
5+
|^
6+
|MACRO ERROR
7+
-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:20:7 ---------------------------------------------------------
8+
19 |@error
9+
20 |object oGlobal // error
10+
|^
11+
|MACRO ERROR
12+
-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:24:6 ---------------------------------------------------------
13+
23 | @error
14+
24 | val vMember: Int = 1 // error
515
| ^
616
| MACRO ERROR
7-
-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:20:11 --------------------------------------------------------
8-
19 | @error
9-
20 | lazy val lvMember: Int = 1 // error
17+
-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:26:11 --------------------------------------------------------
18+
25 | @error
19+
26 | lazy val lvMember: Int = 1 // error
1020
| ^
1121
| MACRO ERROR
12-
-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:22:6 ---------------------------------------------------------
13-
21 | @error
14-
22 | def dMember: Int = 1 // error
22+
-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:28:6 ---------------------------------------------------------
23+
27 | @error
24+
28 | def dMember: Int = 1 // error
1525
| ^
1626
| MACRO ERROR
17-
-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:24:8 ---------------------------------------------------------
18-
23 | @error
19-
24 | given gMember: Int = 1 // error
27+
-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:30:8 ---------------------------------------------------------
28+
29 | @error
29+
30 | given gMember: Int = 1 // error
2030
| ^
2131
| MACRO ERROR
22-
-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:26:8 ---------------------------------------------------------
23-
25 | @error
24-
26 | given gMember2: Num[Int] with // error: object not supported (TODO support)
32+
-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:32:8 ---------------------------------------------------------
33+
31 | @error
34+
32 | given gMember2: Num[Int] with // error
35+
| ^
36+
| MACRO ERROR
37+
33 | def zero = 0
38+
-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:35:8 ---------------------------------------------------------
39+
34 | @error
40+
35 | given gMember3(using DummyImplicit): Num[Int] with // error
41+
| ^
42+
| MACRO ERROR
43+
36 | def zero = 0
44+
-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:39:8 ---------------------------------------------------------
45+
38 | @error
46+
39 | class cMember // error
2547
| ^
26-
| macro annotations are not supported on object
27-
27 | def zero = 0
28-
-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:29:8 ---------------------------------------------------------
29-
28 | @error
30-
29 | given gMember3(using DummyImplicit): Num[Int] with // error: class not supported (TODO support)
48+
| MACRO ERROR
49+
-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:42:9 ---------------------------------------------------------
50+
41 | @error
51+
42 | object oMember // error
3152
| ^
32-
| macro annotations are not supported on class
33-
30 | def zero = 0
34-
-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:34:8 ---------------------------------------------------------
35-
33 | @error
36-
34 | val vLocal: Int = 1 // error
53+
| MACRO ERROR
54+
-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:46:8 ---------------------------------------------------------
55+
45 | @error
56+
46 | val vLocal: Int = 1 // error
3757
| ^
3858
| MACRO ERROR
39-
-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:36:13 --------------------------------------------------------
40-
35 | @error
41-
36 | lazy val lvLocal: Int = 1 // error
59+
-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:48:13 --------------------------------------------------------
60+
47 | @error
61+
48 | lazy val lvLocal: Int = 1 // error
4262
| ^
4363
| MACRO ERROR
44-
-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:38:8 ---------------------------------------------------------
45-
37 | @error
46-
38 | def dLocal: Int = 1 // error
64+
-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:50:8 ---------------------------------------------------------
65+
49 | @error
66+
50 | def dLocal: Int = 1 // error
4767
| ^
4868
| MACRO ERROR
49-
-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:40:10 --------------------------------------------------------
50-
39 | @error
51-
40 | given gLocal: Int = 1 // error
69+
-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:52:10 --------------------------------------------------------
70+
51 | @error
71+
52 | given gLocal: Int = 1 // error
5272
| ^
5373
| MACRO ERROR
54-
-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:42:10 --------------------------------------------------------
55-
41 | @error
56-
42 | given gLocal2: Num[Int] with // error: object not supported (TODO support)
74+
-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:54:10 --------------------------------------------------------
75+
53 | @error
76+
54 | given gLocal2: Num[Int] with // error
5777
| ^
58-
| macro annotations are not supported on object
59-
43 | def zero = 0
60-
-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:45:10 --------------------------------------------------------
61-
44 | @error
62-
45 | given gLocal3(using DummyImplicit): Num[Int] with // error: class not supported (TODO support)
78+
| MACRO ERROR
79+
55 | def zero = 0
80+
-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:57:10 --------------------------------------------------------
81+
56 | @error
82+
57 | given gLocal3(using DummyImplicit): Num[Int] with // error
6383
| ^
64-
| macro annotations are not supported on class
65-
46 | def zero = 0
84+
| MACRO ERROR
85+
58 | def zero = 0
86+
-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:61:10 --------------------------------------------------------
87+
60 | @error
88+
61 | class cLocal // error
89+
| ^
90+
| MACRO ERROR
91+
-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:63:11 --------------------------------------------------------
92+
62 | @error
93+
63 | object oLocal // error
94+
| ^
95+
| MACRO ERROR
6696
-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:2:4 ----------------------------------------------------------
6797
1 |@error
6898
2 |val vGlobal: Int = 1 // error
@@ -85,13 +115,13 @@
85115
|MACRO ERROR
86116
-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:10:6 ---------------------------------------------------------
87117
9 |@error
88-
10 |given gGlobal2: Num[Int] with // error: object not supported (TODO support)
118+
10 |given gGlobal2: Num[Int] with // error
89119
|^
90-
|macro annotations are not supported on object
120+
|MACRO ERROR
91121
11 | def zero = 0
92122
-- Error: tests/neg-macros/annot-error-annot/Test_2.scala:13:6 ---------------------------------------------------------
93123
12 |@error
94-
13 |given gGlobal3(using DummyImplicit): Num[Int] with // error: class not supported (TODO support)
124+
13 |given gGlobal3(using DummyImplicit): Num[Int] with // error
95125
|^
96-
|macro annotations are not supported on class
126+
|MACRO ERROR
97127
14 | def zero = 0

‎tests/neg-macros/annot-error-annot/Test_2.scala‎

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,18 @@ def dGlobal: Int = 1 // error
77
@error
88
given gGlobal: Int = 1 // error
99
@error
10-
given gGlobal2: Num[Int] with // error: object not supported (TODO support)
10+
given gGlobal2: Num[Int] with // error
1111
def zero = 0
1212
@error
13-
given gGlobal3(using DummyImplicit): Num[Int] with // error: class not supported (TODO support)
13+
given gGlobal3(using DummyImplicit): Num[Int] with // error
1414
def zero = 0
1515

16+
@error
17+
class cGlobal // error
18+
19+
@error
20+
object oGlobal // error
21+
1622
class B:
1723
@error
1824
val vMember: Int = 1 // error
@@ -23,12 +29,18 @@ class B:
2329
@error
2430
given gMember: Int = 1 // error
2531
@error
26-
given gMember2: Num[Int] with // error: object not supported (TODO support)
32+
given gMember2: Num[Int] with // error
2733
def zero = 0
2834
@error
29-
given gMember3(using DummyImplicit): Num[Int] with // error: class not supported (TODO support)
35+
given gMember3(using DummyImplicit): Num[Int] with // error
3036
def zero = 0
3137

38+
@error
39+
class cMember // error
40+
41+
@error
42+
object oMember // error
43+
3244
def locals: Unit =
3345
@error
3446
val vLocal: Int = 1 // error
@@ -39,11 +51,16 @@ class B:
3951
@error
4052
given gLocal: Int = 1 // error
4153
@error
42-
given gLocal2: Num[Int] with // error: object not supported (TODO support)
54+
given gLocal2: Num[Int] with // error
4355
def zero = 0
4456
@error
45-
given gLocal3(using DummyImplicit): Num[Int] with // error: class not supported (TODO support)
57+
given gLocal3(using DummyImplicit): Num[Int] with // error
4658
def zero = 0
59+
60+
@error
61+
class cLocal // error
62+
@error
63+
object oLocal // error
4764
()
4865

4966
trait Num[T]:

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /