Skip to content

Commit e4bb0b4

Browse files
Merge pull request #5493 from dotty-staging/add-tasty-reflect-basic-tutorial
Add basic hands on tutorial
2 parents f4d2fef + 906fc1f commit e4bb0b4

File tree

5 files changed

+229
-1
lines changed

5 files changed

+229
-1
lines changed

docs/docs/reference/tasty-reflect.md

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
---
2+
layout: doc-page
3+
title: "TASTy reflect"
4+
---
5+
6+
TASTy Reflect enables inspection and construction of Typed Abstract Syntax Trees (TAST).
7+
It may be used on quoted expressions (`quoted.Expr`) and quoted types (`quoted.Type`) from [Principled Meta-programming](./principled-meta-programming.html)
8+
or on full TASTy files.
9+
10+
If you are writing macros, please first read [Principled Meta-programming](./principled-meta-programming.html).
11+
You may find all you need without using TASTy Reflect.
12+
13+
14+
## From quotes and splices to TASTs Reflect trees and back
15+
16+
`quoted.Expr` and `quoted.Type` are only meant for generative meta-programming, generation of code without inspecting the ASTs.
17+
[Principled Meta-programming](./principled-meta-programming.html) provides the guarantee that the generation of code 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 macros 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` which return a `reflection.Term` and `reflection.TypeTree` respectively.
36+
It will also import all extractors and methods on TASTy Reflect trees. For example the `Term.Literal(_)` extractor used below.
37+
38+
```scala
39+
def natConstImpl(x: Expr[Int])(implicit reflection: Reflection): Expr[Int] = {
40+
import reflection._
41+
val xTree: Term = x.reflect
42+
xTree match {
43+
case Term.Literal(Constant.Int(n)) =>
44+
if (n <= 0)
45+
throw new QuoteError("Parameter must be natural number")
46+
n.toExpr
47+
case _ =>
48+
throw new QuoteError("Parameter must be a known constant")
49+
}
50+
}
51+
```
52+
53+
To easily know which extractors are needed, the `reflection.Term.show` method returns the string representation of the extractors.
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+
## Inspect a TASTy file
60+
61+
To inspect the TASTy Reflect 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+
object Test {
76+
def main(args: Array[String]): Unit = {
77+
ConsumeTasty("", List("foo.Bar"), new Consumer)
78+
}
79+
}
80+
```
81+
82+
## TASTy Reflect API
83+
84+
TASTy Reflect provides the following types:
85+
86+
```none
87+
+- Tree -+- PackageClause
88+
+- Import
89+
+- Statement -+- Definition --+- PackageDef
90+
| +- ClassDef
91+
| +- TypeDef
92+
| +- DefDef
93+
| +- ValDef
94+
|
95+
+- Term --------+- Ident
96+
+- Select
97+
+- Literal
98+
+- This
99+
+- New
100+
+- NamedArg
101+
+- Apply
102+
+- TypeApply
103+
+- Super
104+
+- Typed
105+
+- Assign
106+
+- Block
107+
+- Lambda
108+
+- If
109+
+- Match
110+
+- Try
111+
+- Return
112+
+- Repeated
113+
+- Inlined
114+
+- SelectOuter
115+
+- While
116+
117+
118+
+- TypeTree ----+- Synthetic
119+
| +- Ident
120+
| +- Select
121+
| +- Project
122+
| +- Singleton
123+
+- TypeOrBoundsTree ---+ +- Refined
124+
| +- Applied
125+
| +- Annotated
126+
| +- And
127+
| +- Or
128+
| +- MatchType
129+
| +- ByName
130+
| +- LambdaTypeTree
131+
| +- Bind
132+
|
133+
+- TypeBoundsTree
134+
+- SyntheticBounds
135+
136+
+- CaseDef
137+
+- TypeCaseDef
138+
139+
+- Pattern --+- Value
140+
+- Bind
141+
+- Unapply
142+
+- Alternative
143+
+- TypeTest
144+
145+
146+
+- NoPrefix
147+
+- TypeOrBounds -+- TypeBounds
148+
|
149+
+- Type -------+- ConstantType
150+
+- SymRef
151+
+- TermRef
152+
+- TypeRef
153+
+- SuperType
154+
+- Refinement
155+
+- AppliedType
156+
+- AnnotatedType
157+
+- AndType
158+
+- OrType
159+
+- MatchType
160+
+- ByNameType
161+
+- ParamRef
162+
+- ThisType
163+
+- RecursiveThis
164+
+- RecursiveType
165+
+- LambdaType[ParamInfo <: TypeOrBounds] -+- MethodType
166+
+- PolyType
167+
+- TypeLambda
168+
169+
+- ImportSelector -+- SimpleSelector
170+
+- RenameSelector
171+
+- OmitSelector
172+
173+
+- Id
174+
175+
+- Signature
176+
177+
+- Position
178+
179+
+- Constant
180+
181+
+- Symbol --+- PackageSymbol
182+
+- ClassSymbol
183+
+- TypeSymbol
184+
+- DefSymbol
185+
+- ValSymbol
186+
+- BindSymbol
187+
+- NoSymbol
188+
189+
Aliases:
190+
# TermOrTypeTree = Term | TypeTree
191+
```
192+
193+
## More Examples
194+
195+
* Start experimenting with TASTy Reflect ([link](https://github.com/nicolasstucki/tasty-reflection-exercise))
196+

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 & 1 deletion
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
@@ -33,7 +34,6 @@ package scala.tasty.reflect
3334
* +- Inlined
3435
* +- SelectOuter
3536
* +- While
36-
* +- DoWhile
3737
*
3838
*
3939
* +- TypeTree ----+- Inferred
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)