-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Add basic hands on tutorial #5493
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
c514aaa
b0408eb
d727a7b
13e5e0d
5b5ef9a
906fc1f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
--- | ||
layout: doc-page | ||
title: "TASTy reflect" | ||
--- | ||
|
||
TASTy reflect provides an API that allows inspection and construction of Typed Abstract Syntax Trees (TAST). | ||
It may be used on quoted expressions (`quoted.Expr`) and types (`quoted.Type`) from [Principled Meta-programming](./principled-meta-programming.html) | ||
nicolasstucki marked this conversation as resolved.
Show resolved
Hide resolved
|
||
or on full TASTy files. | ||
|
||
If you are starting using macros see first [Principled Meta-programming](./principled-meta-programming.html) and then follow with API (if really needed). | ||
nicolasstucki marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
|
||
## From quotes and splices to TASTs and back | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The title is not informative, maybe |
||
|
||
`quoted.Expr` and `quoted.Type` are opaque TASTs. | ||
nicolasstucki marked this conversation as resolved.
Show resolved
Hide resolved
|
||
The opaqueness required in [Principled Meta-programming](./principled-meta-programming.html) provide the guarantee that | ||
nicolasstucki marked this conversation as resolved.
Show resolved
Hide resolved
|
||
the generation of code of the macro will be type correct. | ||
nicolasstucki marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Using TASTy reflect will break these guarantees and may fail at macro expansion time, hence additional explicit check must be done. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This paragraph explains the relationship between There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I also agree. |
||
|
||
|
||
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. | ||
nicolasstucki marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
```scala | ||
import scala.quoted._ | ||
import scala.tasty._ | ||
|
||
inline def natConst(x: Int): Int = ~natConstImpl('(x)) | ||
|
||
def natConstImpl(x: Expr[Int])(implicit reflection: Reflection): Expr[Int] = { | ||
import reflection._ | ||
... | ||
} | ||
``` | ||
|
||
`import reflection._` will provide a `reflect` extension method on `quoted.Expr` and `quoted.Type` with return a `reflection.Term` and `reflection.TypeTree` respectivly. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. typo:
with return type ... ?
will provide the extension method There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "which returns" |
||
It will also import all extractors and methods on TASTy reflect trees. For example the `Term.Literal(_)` extractor used bellow. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
or simply There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
For example, the extractor |
||
To easily know which extractor are needed the `reflection.Term.show` method returns the string representation of the extractors. | ||
nicolasstucki marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
|
||
```scala | ||
def natConstImpl(x: Expr[Int])(implicit reflection: Reflection): Expr[Int] = { | ||
import reflection._ | ||
val xTree: Term = x.reflect | ||
xTree match { | ||
case Term.Literal(Constant.Int(n)) => | ||
if (n <= 0) | ||
throw new QuoteError("Parameter must be natural number") | ||
n.toExpr | ||
case _ => | ||
throw new QuoteError("Parameter must be a known constant") | ||
} | ||
} | ||
``` | ||
|
||
The method `reflection.Term.reify[T]` provides a way to to go back to a `quoted.Expr`. | ||
Note that the type must be set explicitly and that if it does not conform to it an exception will be thrown. | ||
In the code above we could have replaced `n.toExpr` by `xTree.reify[Int]`. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is better illustrated by an example, or just remove it and leave it for more advanced examples. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I left the example out on purpose, to push them to use quotes and splices when possible. |
||
|
||
## ASTs of a TASTy file | ||
|
||
To inspect the TASTy trees of a TASTy file a consumer can be defined in the following way. | ||
nicolasstucki marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
```scala | ||
class Consumer extends TastyConsumer { | ||
final def apply(reflect: Reflection)(root: reflect.Tree): Unit = { | ||
import reflect._ | ||
// Do somthing with the tree | ||
} | ||
} | ||
``` | ||
|
||
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. | ||
|
||
```scala | ||
ConsumeTasty(classpath, List("foo.Bar"), new Consumer) | ||
``` | ||
nicolasstucki marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
## TASTy reflect ASTs | ||
|
||
TASTy reflect provides the following types as defined in `scala.tasty.reflect.Core`. | ||
nicolasstucki marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
```none | ||
+- Tree -+- PackageClause | ||
+- Import | ||
+- Statement -+- Definition --+- PackageDef | ||
| +- ClassDef | ||
| +- TypeDef | ||
| +- DefDef | ||
| +- ValDef | ||
| | ||
+- Term --------+- Ident | ||
+- Select | ||
+- Literal | ||
+- This | ||
+- New | ||
+- NamedArg | ||
+- Apply | ||
+- TypeApply | ||
+- Super | ||
+- Typed | ||
+- Assign | ||
+- Block | ||
+- Lambda | ||
+- If | ||
+- Match | ||
+- Try | ||
+- Return | ||
+- Repeated | ||
+- Inlined | ||
+- SelectOuter | ||
+- While | ||
+- DoWhile | ||
|
||
|
||
+- TypeTree ----+- Synthetic | ||
| +- Ident | ||
| +- Select | ||
| +- Project | ||
| +- Singleton | ||
+- TypeOrBoundsTree ---+ +- Refined | ||
| +- Applied | ||
| +- Annotated | ||
| +- And | ||
| +- Or | ||
| +- MatchType | ||
| +- ByName | ||
| +- LambdaTypeTree | ||
| +- Bind | ||
| | ||
+- TypeBoundsTree | ||
+- SyntheticBounds | ||
|
||
+- CaseDef | ||
+- TypeCaseDef | ||
|
||
+- Pattern --+- Value | ||
+- Bind | ||
+- Unapply | ||
+- Alternative | ||
+- TypeTest | ||
|
||
|
||
+- NoPrefix | ||
+- TypeOrBounds -+- TypeBounds | ||
| | ||
+- Type -------+- ConstantType | ||
+- SymRef | ||
+- TermRef | ||
+- TypeRef | ||
+- SuperType | ||
+- Refinement | ||
+- AppliedType | ||
+- AnnotatedType | ||
+- AndType | ||
+- OrType | ||
+- MatchType | ||
+- ByNameType | ||
+- ParamRef | ||
+- ThisType | ||
+- RecursiveThis | ||
+- RecursiveType | ||
+- LambdaType[ParamInfo <: TypeOrBounds] -+- MethodType | ||
+- PolyType | ||
+- TypeLambda | ||
|
||
+- ImportSelector -+- SimpleSelector | ||
+- RenameSelector | ||
+- OmitSelector | ||
|
||
+- Id | ||
|
||
+- Signature | ||
|
||
+- Position | ||
|
||
+- Constant | ||
|
||
+- Symbol --+- PackageSymbol | ||
+- ClassSymbol | ||
+- TypeSymbol | ||
+- DefSymbol | ||
+- ValSymbol | ||
+- BindSymbol | ||
+- NoSymbol | ||
|
||
Aliases: | ||
# TermOrTypeTree = Term | TypeTree | ||
``` | ||
|
||
## Other resources | ||
nicolasstucki marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
* Start plaing TASTy reflect ([link](https://github.com/nicolasstucki/tasty-reflection-exercise)) | ||
nicolasstucki marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import scala.quoted._ | ||
import scala.tasty._ | ||
|
||
object Macros { | ||
|
||
inline def natConst(x: Int): Int = ~natConstImpl('(x)) | ||
|
||
def natConstImpl(x: Expr[Int])(implicit reflection: Reflection): Expr[Int] = { | ||
import reflection._ | ||
val xTree: Term = x.reflect | ||
xTree match { | ||
case Term.Literal(Constant.Int(n)) => | ||
if (n <= 0) | ||
throw new QuoteError("Parameter must be natural number") | ||
xTree.reify[Int] | ||
case _ => | ||
throw new QuoteError("Parameter must be a known constant") | ||
} | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
|
||
import Macros._ | ||
|
||
object Test { | ||
def main(args: Array[String]): Unit = { | ||
println(natConst(2)) | ||
} | ||
|
||
} |
Uh oh!
There was an error while loading. Please reload this page.