Skip to content

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

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
196 changes: 196 additions & 0 deletions docs/docs/reference/tasty-reflect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
---
layout: doc-page
title: "TASTy reflect"
---

TASTy Reflect enables inspection and construction of Typed Abstract Syntax Trees (TAST).
It may be used on quoted expressions (`quoted.Expr`) and quoted types (`quoted.Type`) from [Principled Meta-programming](./principled-meta-programming.html)
or on full TASTy files.

If you are writing macros, please first read [Principled Meta-programming](./principled-meta-programming.html).
You may find all you need without using TASTy Reflect.


## From quotes and splices to TASTs Reflect trees and back

`quoted.Expr` and `quoted.Type` are only meant for generative meta-programming, generation of code without inspecting the ASTs.
[Principled Meta-programming](./principled-meta-programming.html) provides the guarantee that the generation of code will be type-correct.
Using TASTy Reflect will break these guarantees and may fail at macro expansion time, hence additional explicit check must be done.


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.

```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` which return a `reflection.Term` and `reflection.TypeTree` respectively.
It will also import all extractors and methods on TASTy Reflect trees. For example the `Term.Literal(_)` extractor used below.

```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")
}
}
```

To easily know which extractors are needed, the `reflection.Term.show` method returns the string representation of the extractors.

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]`.
Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.


## Inspect a TASTy file

To inspect the TASTy Reflect trees of a TASTy file a consumer can be defined in the following way.

```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
object Test {
def main(args: Array[String]): Unit = {
ConsumeTasty("", List("foo.Bar"), new Consumer)
}
}
```

## TASTy Reflect API

TASTy Reflect provides the following types:

```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


+- 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
```

## More Examples

* Start experimenting with TASTy Reflect ([link](https://github.com/nicolasstucki/tasty-reflection-exercise))

2 changes: 2 additions & 0 deletions docs/sidebar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ sidebar:
url: docs/reference/inline.html
- title: Meta Programming
url: docs/reference/principled-meta-programming.html
- title: TASTy Reflect
url: docs/reference/tasty-reflect.html
- title: Opaque Type Aliases
url: docs/reference/opaques.html
- title: By-Name Implicits
Expand Down
2 changes: 1 addition & 1 deletion library/src/scala/tasty/reflect/Core.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package scala.tasty.reflect

// Keep doc in syncwith docs/docs/reference/tasty-reflect.md
/** Tasty reflect abstract types
*
* ```none
Expand Down Expand Up @@ -33,7 +34,6 @@ package scala.tasty.reflect
* +- Inlined
* +- SelectOuter
* +- While
* +- DoWhile
*
*
* +- TypeTree ----+- Synthetic
Expand Down
21 changes: 21 additions & 0 deletions tests/run-separate-compilation/tasty-macro-const/quoted_1.scala
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))
}

}