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 1 commit
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
193 changes: 193 additions & 0 deletions docs/docs/reference/tasty-reflect.md
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)
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).


## From quotes and splices to TASTs and back
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The title is not informative, maybe Writing macros with TASTy Reflect?


`quoted.Expr` and `quoted.Type` are opaque TASTs.
The opaqueness required in [Principled Meta-programming](./principled-meta-programming.html) provide the guarantee that
the generation of code of the macro 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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This paragraph explains the relationship between PMP and TASTy Reflect, and the properties that may hold & break. This is an important paragraph, I think it's worth singling out to a separate section, distinct from how to write macros with TASTy Reflect.

Copy link
Contributor

Choose a reason for hiding this comment

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

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: with return -> which return, respectivly -> respectively

return a reflection.Term and reflection.TypeTree respectivly

with return type ... ?

will provide a reflect extension method on quoted.Expr and quoted.Type

will provide the extension method reflect for the type quoted.Expr and quoted.Type?

Copy link
Contributor

Choose a reason for hiding this comment

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TASTy reflect trees.

or simply TASTs, as it's introduced in the beginning. It is better to avoid synonyms to the same thing.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For example the Term.Literal(_) extractor used bellow.

For example, the extractor Term.Literal(_) becomes available after the import.

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


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


## ASTs of a TASTy file

To inspect the TASTy 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
ConsumeTasty(classpath, List("foo.Bar"), new Consumer)
```

## TASTy reflect ASTs

TASTy reflect provides the following types as defined in `scala.tasty.reflect.Core`.

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

* Start plaing 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
1 change: 1 addition & 0 deletions 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
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))
}

}