Skip to content

Commit 68b57b3

Browse files
committed
Add basic hands on tutorial
1 parent 145cf52 commit 68b57b3

File tree

7 files changed

+255
-0
lines changed

7 files changed

+255
-0
lines changed

docs/docs/reference/tasty-reflect.md

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
---
2+
layout: doc-page
3+
title: "TASTy reflect"
4+
---
5+
6+
TASTy reflect provides an API that allows inspection and construction of Typed Abstract Syntax Trees (TAST).
7+
It may be used on quoted expressions (`quoted.Expr`) and types (`quoted.Type`) from [Principled Meta-programming](./principled-meta-programming.html)
8+
or on full TASTy files.
9+
10+
If you are starting using macros see first [Principled Meta-programming](./principled-meta-programming.html) and then follow with API (if really needed).
11+
12+
13+
## From quotes and splices to TASTs and back
14+
15+
`quoted.Expr` and `quoted.Type` are opaque TASTs.
16+
The opaqueness required in [Principled Meta-programming](./principled-meta-programming.html) provide the guarantee that
17+
the generation of code of the macro will be type correct.
18+
Using TASTy reflect will break these guarantees and may fail at macro expansion time, hence additional explicit check must be done.
19+
20+
21+
To provide reflection capabilities in macro we need to add an implicit parameter of type `scala.tasty.Reflection` and import it in the scope where it is used.
22+
23+
```scala
24+
import scala.quoted._
25+
import scala.tasty._
26+
27+
inline def natConst(x: Int): Int = ~natConstImpl('(x))
28+
29+
def natConstImpl(x: Expr[Int])(implicit reflection: Reflection): Expr[Int] = {
30+
import reflection._
31+
...
32+
}
33+
```
34+
35+
`import reflection._` will provide a `reflect` extension method on `quoted.Expr` and `quoted.Type` with return a `reflection.Term` and `reflection.TypeTree` respectivly.
36+
It will also import all extractors and methods on TASTy reflect trees. For example the `Term.Literal(_)` extractor used bellow.
37+
To easily know which extractor are needed the `reflection.Term.show` method returns the string representation of the extractors.
38+
39+
40+
```scala
41+
def natConstImpl(x: Expr[Int])(implicit reflection: Reflection): Expr[Int] = {
42+
import reflection._
43+
val xTree: Term = x.reflect
44+
xTree match {
45+
case Term.Literal(Constant.Int(n)) =>
46+
if (n <= 0)
47+
throw new QuoteError("Parameter must be natural number")
48+
n.toExpr
49+
case _ =>
50+
throw new QuoteError("Parameter must be a known constant")
51+
}
52+
}
53+
```
54+
55+
The method `reflection.Term.reify[T]` provides a way to to go back to a `quoted.Expr`.
56+
Note that the type must be set explicitly and that if it does not conform to it an exception will be thrown.
57+
In the code above we could have replaced `n.toExpr` by `xTree.reify[Int]`.
58+
59+
## ASTs of a TASTy file
60+
61+
To inspect the TASTy trees of a TASTy file a consumer can be defined in the following way.
62+
63+
```scala
64+
class Consumer extends TastyConsumer {
65+
final def apply(reflect: Reflection)(root: reflect.Tree): Unit = {
66+
import reflect._
67+
// Do somthing with the tree
68+
}
69+
}
70+
```
71+
72+
Then the consumer can be instantiated with the following code to get the tree of the class `foo.Bar` for a foo in the classpath.
73+
74+
```scala
75+
ConsumeTasty(classpath, List("foo.Bar"), new Consumer)
76+
```
77+
78+
## TASTy reflect ASTs
79+
80+
TASTy reflect provides the following types as defined in `scala.tasty.reflect.Core`.
81+
82+
```none
83+
+- Tree -+- PackageClause
84+
+- Import
85+
+- Statement -+- Definition --+- PackageDef
86+
| +- ClassDef
87+
| +- TypeDef
88+
| +- DefDef
89+
| +- ValDef
90+
|
91+
+- Term --------+- Ident
92+
+- Select
93+
+- Literal
94+
+- This
95+
+- New
96+
+- NamedArg
97+
+- Apply
98+
+- TypeApply
99+
+- Super
100+
+- Typed
101+
+- Assign
102+
+- Block
103+
+- Lambda
104+
+- If
105+
+- Match
106+
+- Try
107+
+- Return
108+
+- Repeated
109+
+- Inlined
110+
+- SelectOuter
111+
+- While
112+
+- DoWhile
113+
114+
115+
+- TypeTree ----+- Synthetic
116+
| +- Ident
117+
| +- Select
118+
| +- Project
119+
| +- Singleton
120+
+- TypeOrBoundsTree ---+ +- Refined
121+
| +- Applied
122+
| +- Annotated
123+
| +- And
124+
| +- Or
125+
| +- MatchType
126+
| +- ByName
127+
| +- LambdaTypeTree
128+
| +- Bind
129+
|
130+
+- TypeBoundsTree
131+
+- SyntheticBounds
132+
133+
+- CaseDef
134+
+- TypeCaseDef
135+
136+
+- Pattern --+- Value
137+
+- Bind
138+
+- Unapply
139+
+- Alternative
140+
+- TypeTest
141+
142+
143+
+- NoPrefix
144+
+- TypeOrBounds -+- TypeBounds
145+
|
146+
+- Type -------+- ConstantType
147+
+- SymRef
148+
+- TermRef
149+
+- TypeRef
150+
+- SuperType
151+
+- Refinement
152+
+- AppliedType
153+
+- AnnotatedType
154+
+- AndType
155+
+- OrType
156+
+- MatchType
157+
+- ByNameType
158+
+- ParamRef
159+
+- ThisType
160+
+- RecursiveThis
161+
+- RecursiveType
162+
+- LambdaType[ParamInfo <: TypeOrBounds] -+- MethodType
163+
+- PolyType
164+
+- TypeLambda
165+
166+
+- ImportSelector -+- SimpleSelector
167+
+- RenameSelector
168+
+- OmitSelector
169+
170+
+- Id
171+
172+
+- Signature
173+
174+
+- Position
175+
176+
+- Constant
177+
178+
+- Symbol --+- PackageSymbol
179+
+- ClassSymbol
180+
+- TypeSymbol
181+
+- DefSymbol
182+
+- ValSymbol
183+
+- BindSymbol
184+
+- NoSymbol
185+
186+
Aliases:
187+
# TermOrTypeTree = Term | TypeTree
188+
```
189+
190+
## Other resources
191+
192+
* Start plaing TASTy reflect ([link](https://github.com/nicolasstucki/tasty-reflection-exercise))
193+

docs/sidebar.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ sidebar:
5151
url: docs/reference/inline.html
5252
- title: Meta Programming
5353
url: docs/reference/principled-meta-programming.html
54+
- title: TASTy Reflect
55+
url: docs/reference/tasty-reflect.html
5456
- title: Opaque Type Aliases
5557
url: docs/reference/opaques.html
5658
- title: By-Name Implicits

library/src/scala/tasty/reflect/Core.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package scala.tasty.reflect
22

3+
// Keep doc in syncwith docs/docs/reference/tasty-reflect.md
34
/** Tasty reflect abstract types
45
*
56
* ```none
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import scala.quoted._
2+
import scala.tasty._
3+
4+
object Macros {
5+
6+
inline def natConst(x: Int): Int = ~natConstImpl('(x))
7+
8+
def natConstImpl(x: Expr[Int])(implicit reflection: Reflection): Expr[Int] = {
9+
import reflection._
10+
val xTree: Term = x.reflect
11+
xTree match {
12+
case Term.Literal(Constant.Int(n)) =>
13+
if (n <= 0)
14+
throw new QuoteError("Parameter must be natural number")
15+
xTree.reify[Int]
16+
case _ =>
17+
throw new QuoteError("Parameter must be a known constant")
18+
}
19+
}
20+
21+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
2+
import Macros._
3+
4+
object Test {
5+
def main(args: Array[String]): Unit = {
6+
natConst(-2) // error
7+
}
8+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import scala.quoted._
2+
import scala.tasty._
3+
4+
object Macros {
5+
6+
inline def natConst(x: Int): Int = ~natConstImpl('(x))
7+
8+
def natConstImpl(x: Expr[Int])(implicit reflection: Reflection): Expr[Int] = {
9+
import reflection._
10+
val xTree: Term = x.reflect
11+
xTree match {
12+
case Term.Literal(Constant.Int(n)) =>
13+
if (n <= 0)
14+
throw new QuoteError("Parameter must be natural number")
15+
xTree.reify[Int]
16+
case _ =>
17+
throw new QuoteError("Parameter must be a known constant")
18+
}
19+
}
20+
21+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
2+
import Macros._
3+
4+
object Test {
5+
def main(args: Array[String]): Unit = {
6+
println(natConst(2))
7+
}
8+
9+
}

0 commit comments

Comments
 (0)