{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Scalameta Tree Guide\n", "\n", "*This notebook is based on the [Scalameta Tree Guide](https://scalameta.org/docs/trees/guide.html) from the Scalameta documenation*.\n", "\n", "A core functionality of Scalameta is syntax trees, which enable you to read,\n", "analyze, transform and generate Scala programs at a level of abstraction. In\n", "this guide, you will learn how to\n", "\n", "- parse source code into syntax trees\n", "- construct new syntax trees\n", "- pattern match syntax trees\n", "- traverse syntax trees\n", "- transform syntax trees\n", "\n", "## Installation\n", "\n", "Add a dependency to Scalameta in your build to get started. Scalameta supports\n", "Scala 2.11, Scala 2.12, Scala.js and Scala Native.\n", "\n", "### sbt" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```scala\n", "// build.sbt\n", "libraryDependencies += \"org.scalameta\" %% \"scalameta\" % \"4.2.3\"\n", "\n", "// For Scala.js, Scala Native\n", "libraryDependencies += \"org.scalameta\" %%% \"scalameta\" % \"4.2.3\"\n", "```\n", "\n", "[![Maven Central](https://maven-badges.herokuapp.com/maven-central/org.scalameta/scalameta_2.13/badge.svg)](https://maven-badges.herokuapp.com/maven-central/org.scalameta/scalameta_2.13)\n", "\n", "All code examples assume you have the following import\n", "\n", "```scala\n", "import scala.meta._\n", "\n", "import scalafix.v1._\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Ammonite REPL or Jupyter Notebook with Almond\n", "\n", "A great way to experiment with Scalameta is to use the\n", "[Ammonite REPL](http://ammonite.io/#Ammonite-REPL) or [Almond](https://almond.sh)." ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "attributes": { "classes": [ "scala" ], "id": "" } }, "outputs": [ { "data": { "text/plain": [ "\u001b[32mimport \u001b[39m\u001b[36m$ivy.$ , scala.meta._\u001b[39m" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "// Ammonite REPL\n", "import $ivy.`org.scalameta::scalameta:4.2.3`, scala.meta._" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### ScalaFiddle\n", "\n", "You can try out Scalameta online with the\n", "[ScalaFiddle playground](scalafiddle.html).\n", "\n", "## What is a syntax tree?\n", "\n", "Syntax trees are a representation of source code that makes it easier to\n", "programmatically analyze programs. Scalameta has syntax trees that represent\n", "Scala programs.\n", "\n", "![](assets/tree.svg)\n", "\n", "Scalameta trees are **lossless**, meaning that they represent Scala programs in\n", "sufficient to go from text to trees and vice-versa. Lossless syntax trees are\n", "great for fine-grained analysis of source code, which is useful for a range of\n", "applications including formatting, refactoring, linting and documentation tools\n", "\n", "## Parse trees\n", "\n", "Scalameta comes with a parser to produce syntax trees from Scala source code.\n", "You can parse trees from a variety of sources into different kinds of tree\n", "nodes.\n", "\n", "### From strings\n", "\n", "The simplest way to parse source code is from a string. As long as you have\n", "`import scala.meta._` in your scope, you can use the `parse[Source]` extension\n", "method" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\u001b[36mprogram\u001b[39m: \u001b[32mString\u001b[39m = \u001b[32m\"object Main extends App { print(\\\"Hello!\\\") }\"\u001b[39m\n", "\u001b[36mtree\u001b[39m: \u001b[32mSource\u001b[39m = \u001b[33mSource\u001b[39m(\n", " \u001b[33mList\u001b[39m(\n", " \u001b[33mDefn.Object\u001b[39m(\n", " \u001b[33mList\u001b[39m(),\n", " \u001b[33mTerm.Name\u001b[39m(\u001b[32m\"Main\"\u001b[39m),\n", " \u001b[33mTemplate\u001b[39m(\n", " \u001b[33mList\u001b[39m(),\n", " \u001b[33mList\u001b[39m(\u001b[33mInit\u001b[39m(\u001b[33mType.Name\u001b[39m(\u001b[32m\"App\"\u001b[39m), , \u001b[33mList\u001b[39m())),\n", " \u001b[33mSelf\u001b[39m(, \u001b[32mNone\u001b[39m),\n", " \u001b[33mList\u001b[39m(\u001b[33mTerm.Apply\u001b[39m(\u001b[33mTerm.Name\u001b[39m(\u001b[32m\"print\"\u001b[39m), \u001b[33mList\u001b[39m(\u001b[33mLit.String\u001b[39m(\u001b[32m\"Hello!\"\u001b[39m))))\n", " )\n", " )\n", " )\n", ")" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "val program = \"\"\"object Main extends App { print(\"Hello!\") }\"\"\"\n", "val tree = program.parse[Source].get" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Once parsed, you can print the tree back into its original source code" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "object Main extends App { print(\"Hello!\") }\n" ] } ], "source": [ "println(tree.syntax)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The problem with parsing from strings it that error messages don't include a\n", "filename" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ ":1: error: } expected but end of file found\n", "object Main {\n", " ^\n" ] } ], "source": [ "println(\n", " \"object Main {\".parse[Source]\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To make error messages more helpful it's recommended to always use virtual files\n", "when possible, as explained below.\n", "\n", "### From files\n", "\n", "To parse a file into a tree it's recommended to first read the file contents\n", "into a string and then construct a virtual file" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\u001b[36mpath\u001b[39m: \u001b[32mjava\u001b[39m.\u001b[32mnio\u001b[39m.\u001b[32mfile\u001b[39m.\u001b[32mPath\u001b[39m = example.scala\n", "\u001b[36mbytes\u001b[39m: \u001b[32mArray\u001b[39m[\u001b[32mByte\u001b[39m] = \u001b[33mArray\u001b[39m(\n", " \u001b[32m111\u001b[39m,\n", " \u001b[32m98\u001b[39m,\n", " \u001b[32m106\u001b[39m,\n", " \u001b[32m101\u001b[39m,\n", " \u001b[32m99\u001b[39m,\n", " \u001b[32m116\u001b[39m,\n", " \u001b[32m32\u001b[39m,\n", " \u001b[32m69\u001b[39m,\n", " \u001b[32m120\u001b[39m,\n", " \u001b[32m97\u001b[39m,\n", " \u001b[32m109\u001b[39m,\n", " \u001b[32m112\u001b[39m,\n", " \u001b[32m108\u001b[39m,\n", " \u001b[32m101\u001b[39m,\n", " \u001b[32m32\u001b[39m,\n", " \u001b[32m101\u001b[39m,\n", " \u001b[32m120\u001b[39m,\n", " \u001b[32m116\u001b[39m,\n", " \u001b[32m101\u001b[39m,\n", " \u001b[32m110\u001b[39m,\n", " \u001b[32m100\u001b[39m,\n", " \u001b[32m115\u001b[39m,\n", " \u001b[32m32\u001b[39m,\n", " \u001b[32m65\u001b[39m,\n", " \u001b[32m112\u001b[39m,\n", " \u001b[32m112\u001b[39m,\n", " \u001b[32m32\u001b[39m,\n", " \u001b[32m123\u001b[39m,\n", " \u001b[32m32\u001b[39m,\n", " \u001b[32m112\u001b[39m,\n", " \u001b[32m114\u001b[39m,\n", " \u001b[32m105\u001b[39m,\n", " \u001b[32m110\u001b[39m,\n", " \u001b[32m116\u001b[39m,\n", " \u001b[32m108\u001b[39m,\n", " \u001b[32m110\u001b[39m,\n", " \u001b[32m40\u001b[39m,\n", " \u001b[32m34\u001b[39m,\n", "...\n", "\u001b[36mtext\u001b[39m: \u001b[32mString\u001b[39m = \u001b[32m\"\"\"object Example extends App { println(\"Hello from a file!\") }\n", "\"\"\"\u001b[39m\n", "\u001b[36minput\u001b[39m: \u001b[32minputs\u001b[39m.\u001b[32mInput\u001b[39m.\u001b[32mVirtualFile\u001b[39m = \u001b[33mVirtualFile\u001b[39m(\n", " \u001b[32m\"example.scala\"\u001b[39m,\n", " \u001b[32m\"\"\"object Example extends App { println(\"Hello from a file!\") }\n", "\"\"\"\u001b[39m\n", ")\n", "\u001b[36mexampleTree\u001b[39m: \u001b[32mSource\u001b[39m = \u001b[33mSource\u001b[39m(\n", " \u001b[33mList\u001b[39m(\n", " \u001b[33mDefn.Object\u001b[39m(\n", " \u001b[33mList\u001b[39m(),\n", " \u001b[33mTerm.Name\u001b[39m(\u001b[32m\"Example\"\u001b[39m),\n", " \u001b[33mTemplate\u001b[39m(\n", " \u001b[33mList\u001b[39m(),\n", " \u001b[33mList\u001b[39m(\u001b[33mInit\u001b[39m(\u001b[33mType.Name\u001b[39m(\u001b[32m\"App\"\u001b[39m), , \u001b[33mList\u001b[39m())),\n", " \u001b[33mSelf\u001b[39m(, \u001b[32mNone\u001b[39m),\n", " \u001b[33mList\u001b[39m(\n", " \u001b[33mTerm.Apply\u001b[39m(\n", " \u001b[33mTerm.Name\u001b[39m(\u001b[32m\"println\"\u001b[39m),\n", " \u001b[33mList\u001b[39m(\u001b[33mLit.String\u001b[39m(\u001b[32m\"Hello from a file!\"\u001b[39m))\n", " )\n", " )\n", " )\n", " )\n", " )\n", ")" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "val path = java.nio.file.Paths.get(\"example.scala\")\n", "val bytes = java.nio.file.Files.readAllBytes(path)\n", "val text = new String(bytes, \"UTF-8\")\n", "val input = Input.VirtualFile(path.toString, text)\n", "val exampleTree = input.parse[Source].get" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "object Example extends App { println(\"Hello from a file!\") }\n" ] } ], "source": [ "print(exampleTree.syntax)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The difference between `text.parse[Source]` and `input.parse[Source]` is that\n", "the filename appear in error messages for `Input.VirtualFile`." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "example.scala:1: error: } expected but end of file found\n", "object Main {\n", " ^\n" ] } ], "source": [ "println(\n", " Input.VirtualFile(\"example.scala\", \"object Main {\").parse[Source]\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### From expressions\n", "\n", "To parse a simple expressions such as `a + b` use `parse[Stat]` The name `Stat`\n", "stands for \"statement\"." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Term.ApplyInfix(Term.Name(\"a\"), Term.Name(\"+\"), Nil, List(Term.Name(\"b\")))\n" ] } ], "source": [ "println(\"a + b\".parse[Stat].get.structure)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If we try to parse an expression with `parse[Source]` we get an error because\n", "`a + b` is not valid at the top-level for Scala programs" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ ":1: error: expected class or object definition\n", "a + b\n", "^\n" ] } ], "source": [ "println(\"a + b\".parse[Source])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The same solution can be used to parse other tree nodes such as types" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Type.With(Type.Name(\"A\"), Type.Name(\"B\"))\n" ] } ], "source": [ "println(\"A with B\".parse[Type].get.structure)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If we use `parse[Stat]` to parse types we get an error" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ ":1: error: end of file expected but with found\n", "A with B\n", " ^\n" ] } ], "source": [ "println(\"A with B\".parse[Stat])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### From programs with multiple top-level statements\n", "\n", "To parse programs with multiple top-level statements such as `build.sbt` files\n", "or Ammonite scripts we use the `Sbt1` dialect. By default, we get an error when\n", "using `parse[Source]`." ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\u001b[36mbuildSbt\u001b[39m: \u001b[32mString\u001b[39m = \u001b[32m\"\"\"\n", "val core = project\n", "val cli = project.dependsOn(core)\n", "\"\"\"\u001b[39m" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "val buildSbt = \"\"\"\n", "val core = project\n", "val cli = project.dependsOn(core)\n", "\"\"\"" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ ":2: error: expected class or object definition\n", "val core = project\n", "^\n" ] } ], "source": [ "println(buildSbt.parse[Source])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This error happens because vals are not allowed as top-level statements in\n", "normal Scala programs. To fix this problem, wrap the input with `dialects.Sbt1`" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "List(val core = project, val cli = project.dependsOn(core))\n" ] } ], "source": [ "println(dialects.Sbt1(buildSbt).parse[Source].get.stats)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The same solution works for virtual files" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "List(val core = project, val cli = project.dependsOn(core))\n" ] } ], "source": [ "println(\n", " dialects.Sbt1(\n", " Input.VirtualFile(\"build.sbt\", buildSbt)\n", " ).parse[Source].get.stats\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The difference between `dialects.Sbt1(input)` and `parse[Stat]` is that\n", "`parse[Stat]` does not allow multiple top-level statements" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ ":3: error: end of file expected but val found\n", "val cli = project.dependsOn(core)\n", "^\n" ] } ], "source": [ "println(buildSbt.parse[Stat])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that `dialects.Sbt1` does not accept programs with package declarations" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ ":1: error: illegal start of definition\n", "package library; object Main\n", "^\n" ] } ], "source": [ "println(\n", " dialects.Sbt1(\"package library; object Main\").parse[Source]\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Construct trees\n", "\n", "Sometimes we need to dynamically construct syntax trees instead of parsing them\n", "from source code. There are two primary ways to construct trees: normal\n", "constructors and quasiquotes.\n", "\n", "### With normal constructors\n", "\n", "Normal tree constructors as plain functions" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "function(argument)\n" ] } ], "source": [ "println(Term.Apply(Term.Name(\"function\"), List(Term.Name(\"argument\"))))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Although normal constructors are verbose, they give most flexibility when\n", "constructing trees.\n", "\n", "To learn tree node names you can use `.structure` on existing tree nodes" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Term.Apply(Term.Name(\"function\"), List(Term.Name(\"argument\")))\n" ] } ], "source": [ "println(\"function(argument)\".parse[Stat].get.structure)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The output of structure is safe to copy-paste into programs.\n", "\n", "Another good way to learn the structure of trees is\n", "[AST Explorer](http://astexplorer.net/#/gist/ec56167ffafb20cbd8d68f24a37043a9/97da19c8212688ceb232708b67228e3839dadc7c).\n", "\n", "### With quasiquotes\n", "\n", "Quasiquotes are string interpolators that expand at compile-time into normal\n", "constructor calls" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Term.Apply(Term.Name(\"function\"), List(Term.Name(\"argument\")))\n" ] } ], "source": [ "println(q\"function(argument)\".structure)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can write multiline quasiquotes to construct large programs" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Defn.Object(Nil, Term.Name(\"Example\"), Template(Nil, List(Init(Type.Name(\"App\"), Name(\"\"), Nil)), Self(Name(\"\"), None), List(Term.Apply(Term.Name(\"println\"), List(Lit.Int(42))))))\n" ] } ], "source": [ "println(\n", " q\"\"\"\n", " object Example extends App {\n", " println(42)\n", " }\n", " \"\"\".structure\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "> It's important to keep in mind that quasiquotes expand at compile-time into\n", "> the same program as if you had written normal constructors by hand. This means\n", "> for example that formatting details or comments are not preserved" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "function(argument)\n" ] } ], "source": [ "println(q\"function ( argument ) // comment\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Quasiquotes can be composed together like normal string interpolators with\n", "dollar splices `$`" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Left() + Right()\n" ] }, { "data": { "text/plain": [ "\u001b[36mleft\u001b[39m: \u001b[32mTerm\u001b[39m.\u001b[32mApply\u001b[39m = \u001b[33mTerm.Apply\u001b[39m(\u001b[33mTerm.Name\u001b[39m(\u001b[32m\"Left\"\u001b[39m), \u001b[33mList\u001b[39m())\n", "\u001b[36mright\u001b[39m: \u001b[32mTerm\u001b[39m.\u001b[32mApply\u001b[39m = \u001b[33mTerm.Apply\u001b[39m(\u001b[33mTerm.Name\u001b[39m(\u001b[32m\"Right\"\u001b[39m), \u001b[33mList\u001b[39m())" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "val left = q\"Left()\"\n", "val right = q\"Right()\"\n", "println(q\"$left + $right\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A list of trees can be inserted into a quasiquote with double dots `..$`" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "function(arg1, arg2)\n" ] }, { "data": { "text/plain": [ "\u001b[36marguments\u001b[39m: \u001b[32mList\u001b[39m[\u001b[32mTerm\u001b[39m.\u001b[32mName\u001b[39m] = \u001b[33mList\u001b[39m(\u001b[33mTerm.Name\u001b[39m(\u001b[32m\"arg1\"\u001b[39m), \u001b[33mTerm.Name\u001b[39m(\u001b[32m\"arg2\"\u001b[39m))" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "val arguments = List(q\"arg1\", q\"arg2\")\n", "println(q\"function(..$arguments)\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A curried argument argument lists can be inserted into a quasiquotes with triple\n", "dots `...$`" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "function(arg1, arg2)(arg3, arg4)\n" ] }, { "data": { "text/plain": [ "\u001b[36marguments2\u001b[39m: \u001b[32mList\u001b[39m[\u001b[32mTerm\u001b[39m.\u001b[32mName\u001b[39m] = \u001b[33mList\u001b[39m(\u001b[33mTerm.Name\u001b[39m(\u001b[32m\"arg3\"\u001b[39m), \u001b[33mTerm.Name\u001b[39m(\u001b[32m\"arg4\"\u001b[39m))\n", "\u001b[36mallArguments\u001b[39m: \u001b[32mList\u001b[39m[\u001b[32mList\u001b[39m[\u001b[32mTerm\u001b[39m.\u001b[32mName\u001b[39m]] = \u001b[33mList\u001b[39m(\n", " \u001b[33mList\u001b[39m(\u001b[33mTerm.Name\u001b[39m(\u001b[32m\"arg1\"\u001b[39m), \u001b[33mTerm.Name\u001b[39m(\u001b[32m\"arg2\"\u001b[39m)),\n", " \u001b[33mList\u001b[39m(\u001b[33mTerm.Name\u001b[39m(\u001b[32m\"arg3\"\u001b[39m), \u001b[33mTerm.Name\u001b[39m(\u001b[32m\"arg4\"\u001b[39m))\n", ")" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "val arguments2 = List(q\"arg3\", q\"arg4\")\n", "val allArguments = List(arguments, arguments2)\n", "println(q\"function(...$allArguments)\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A common mistake is to splice an empty type parameter list into type application\n", "nodes . Imagine we have a list of type arguments that happens to be empty" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\u001b[36mtypeArguments\u001b[39m: \u001b[32mList\u001b[39m[\u001b[32mType\u001b[39m] = \u001b[33mList\u001b[39m()" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "val typeArguments = List.empty[Type]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If we directly splice the lists into a type application we get a cryptic error\n", "message \"invariant failed\"" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "ename": "", "evalue": "", "output_type": "error", "traceback": [ "\u001b[31morg.scalameta.invariants.InvariantFailedException: invariant failed:\nwhen verifying targs.!=(null).&&(targs.nonEmpty)\nfound that targs.nonEmpty is false\nwhere targs = List()\u001b[39m\n org.scalameta.invariants.InvariantFailedException$.raise(\u001b[32mExceptions.scala\u001b[39m:\u001b[32m15\u001b[39m)\n scala.meta.Term$ApplyType$.internal$49(\u001b[32mTrees.scala\u001b[39m:\u001b[32m82\u001b[39m)\n scala.meta.Term$ApplyType$.apply(\u001b[32mTrees.scala\u001b[39m:\u001b[32m82\u001b[39m)\n ammonite.$sess.cmd26$Helper.(\u001b[32mcmd26.sc\u001b[39m:\u001b[32m1\u001b[39m)\n ammonite.$sess.cmd26$.(\u001b[32mcmd26.sc\u001b[39m:\u001b[32m7\u001b[39m)\n ammonite.$sess.cmd26$.(\u001b[32mcmd26.sc\u001b[39m:\u001b[32m-1\u001b[39m)" ] } ], "source": [ "q\"function[..$typeArguments]()\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The quasiquote above is equivalent to calling the normal constructor\n", "`Type.ApplyType(.., typeArguments)`. Scalameta trees perform strict runtime\n", "validation for invariants such as \"type application arguments must be\n", "non-empty\". To fix this problem, guard the splice against the length of the list" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Term.Apply(Term.Name(\"function\"), Nil)\n" ] } ], "source": [ "println(\n", " (if (typeArguments.isEmpty) q\"function()\"\n", " else q\"function[..$typeArguments]()\").structure\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To learn more about quasiquotes, consult the\n", "[quasiquote spec](quasiquotes.html).\n", "\n", "## Pattern match trees\n", "\n", "Use pattern matching to target interesting tree nodes and deconstruct them. A\n", "core design principle of Scalameta trees is that tree pattern matching is the\n", "dual of tree construction. If you know how to construct a tree, you know how to\n", "de-construct it.\n", "\n", "### With normal constructors\n", "\n", "Normal constructors work in pattern position the same way they work in regular\n", "term position." ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1 function\n", "2 arg1\n", "3 arg2\n" ] } ], "source": [ "\"function(arg1, arg2)\".parse[Term].get match {\n", " case Term.Apply(function, List(arg1, arg2)) =>\n", " println(\"1 \" + function)\n", " println(\"2 \" + arg1)\n", " println(\"3 \" + arg2)\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Repeated fields are always `List[T]`, so you can safely deconstruct trees with\n", "the `List(arg1, arg2)` syntax or if you prefer the `arg1 :: arg2 :: Nil` syntax.\n", "There is no need to use `Seq(arg1, arg2)` or `arg1 +: arg2 +: Nil`.\n", "\n", "### With quasiquotes\n", "\n", "Quasiquotes expand at compile-time and work the same way in pattern position as\n", "in term position." ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1 function\n", "2 List(arg1, arg2)\n" ] } ], "source": [ "Term.Apply(\n", " Term.Name(\"function\"),\n", " List(Term.Name(\"arg1\"), Term.Name(\"arg2\"))\n", ") match {\n", " case q\"$function(..$args)\" =>\n", " println(\"1 \" + function)\n", " println(\"2 \" + args)\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Use triple dollar splices `...$` to extract curried argument lists" ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1 function\n", "2 List(List(arg1, arg2), List(arg3, arg4))\n" ] } ], "source": [ "\"function(arg1, arg2)(arg3, arg4)\".parse[Term].get match {\n", " case q\"$function(...$args)\" =>\n", " println(\"1 \" + function)\n", " println(\"2 \" + args)\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "> Pattern matching with quasiquotes is generally discouraged because it's easy\n", "> to write patterns that result in unintended match errors." ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [ { "ename": "", "evalue": "", "output_type": "error", "traceback": [ "\u001b[31mscala.MatchError: final val x = 2 (of class scala.meta.Defn$Val$DefnValImpl)\u001b[39m\n ammonite.$sess.cmd31$Helper.(\u001b[32mcmd31.sc\u001b[39m:\u001b[32m1\u001b[39m)\n ammonite.$sess.cmd31$.(\u001b[32mcmd31.sc\u001b[39m:\u001b[32m7\u001b[39m)\n ammonite.$sess.cmd31$.(\u001b[32mcmd31.sc\u001b[39m:\u001b[32m-1\u001b[39m)" ] } ], "source": [ "q\"final val x = 2\" match {\n", " case q\"val x = 2\" => // boom!\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To fix this pattern, we specify that the `final` modifier should be ignored\n", "using `$_`" ] }, { "cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "OK\n" ] } ], "source": [ "q\"final val x = 2\" match {\n", " case q\"$_ val x = 2\" => println(\"OK\")\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Compare trees for equality\n", "\n", "Scalameta trees use reference equality by default, which may result in\n", "surprising behavior. A common mistake is to use `==` between parsed syntax trees\n", "and quasiquotes" ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\u001b[36mres33\u001b[39m: \u001b[32mBoolean\u001b[39m = false" ] }, "execution_count": 34, "metadata": {}, "output_type": "execute_result" } ], "source": [ "\"true\".parse[Term].get == q\"true\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Comparing trees by `==` is the same as comparing them with `eq`. Even identical\n", "quasiquotes produce different references" ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\u001b[36mres34\u001b[39m: \u001b[32mBoolean\u001b[39m = false" ] }, "execution_count": 35, "metadata": {}, "output_type": "execute_result" } ], "source": [ "q\"true\" == q\"true\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Equality checks with `==` will only return true when the reference is the same.j" ] }, { "cell_type": "code", "execution_count": 36, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\u001b[36mtreeReference\u001b[39m: \u001b[32mLit\u001b[39m.\u001b[32mBoolean\u001b[39m = \u001b[33mLit.Boolean\u001b[39m(true)\n", "\u001b[36mres35_1\u001b[39m: \u001b[32mBoolean\u001b[39m = true" ] }, "execution_count": 36, "metadata": {}, "output_type": "execute_result" } ], "source": [ "{ val treeReference = q\"true\"\n", " treeReference == treeReference }" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The idiomatic way to compare trees for structural equality is to use pattern\n", "matching" ] }, { "cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "YAY!\n" ] } ], "source": [ "q\"true\" match { case q\"true\" => println(\"YAY!\") }" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you can't use pattern matching to compare trees by structural equality, you\n", "can use `.structure`" ] }, { "cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\u001b[36mres37\u001b[39m: \u001b[32mBoolean\u001b[39m = true" ] }, "execution_count": 38, "metadata": {}, "output_type": "execute_result" } ], "source": [ "q\"true\".structure == q\"true\".structure" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `.structure` method produces large strings for large programs, which may\n", "become prohibitively slow. The Scalameta contrib module contains a more\n", "efficient `isEqual` helper method to compare trees structurally." ] }, { "cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "cmd38.sc:1: object contrib is not a member of package meta\n", "import scala.meta.contrib._\n", " ^cmd38.sc:2: value isEqual is not a member of meta.Lit.Boolean\n", "val res38_1 = q\"true\".isEqual(q\"true\")\n", " ^Compilation Failed" ] }, { "ename": "", "evalue": "", "output_type": "error", "traceback": [ "Compilation Failed" ] } ], "source": [ "import scala.meta.contrib._\n", "q\"true\".isEqual(q\"true\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Traverse trees\n", "\n", "Scalameta includes utilities to recursively visit tree nodes for both simple and\n", "advanced use-cases. Simple use-cases have high-level APIs that require minimal\n", "ceremony while advanced use-cases use lower-level APIs that typically involve\n", "more side-effects.\n", "\n", "### Simple traversals\n", "\n", "Use `.traverse` to visit every tree node and perform a side-effect, similarly to\n", "`.foreach`" ] }, { "cell_type": "code", "execution_count": 39, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Defn.Val: val x = 2\n", "Pat.Var: x\n", "Term.Name: x\n", "Lit.Int: 2\n" ] } ], "source": [ "q\"val x = 2\".traverse {\n", " case node =>\n", " println(s\"${node.productPrefix}: $node\")\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Use `.collect` to visit every tree node and collect a value instead of\n", "performing a side-effect" ] }, { "cell_type": "code", "execution_count": 40, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\u001b[36mres39\u001b[39m: \u001b[32mList\u001b[39m[(\u001b[32mString\u001b[39m, \u001b[32mString\u001b[39m)] = \u001b[33mList\u001b[39m(\n", " (\u001b[32m\"Defn.Val\"\u001b[39m, \u001b[32m\"val x = 2\"\u001b[39m),\n", " (\u001b[32m\"Pat.Var\"\u001b[39m, \u001b[32m\"x\"\u001b[39m),\n", " (\u001b[32m\"Term.Name\"\u001b[39m, \u001b[32m\"x\"\u001b[39m),\n", " (\u001b[32m\"Lit.Int\"\u001b[39m, \u001b[32m\"2\"\u001b[39m)\n", ")" ] }, "execution_count": 40, "metadata": {}, "output_type": "execute_result" } ], "source": [ "q\"val x = 2\".collect {\n", " case node => node.productPrefix -> node.toString\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The methods `.traverse` and `.collect` don't support customizing the recursion.\n", "For more fine-grained control over which tree nodes to visit implement a custom\n", "`Traverser`.\n", "\n", "### Custom traversals\n", "\n", "Extend `Traverser` if you need to implement a custom tree traversal" ] }, { "cell_type": "code", "execution_count": 41, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\u001b[36mtraverser\u001b[39m: \u001b[32mTraverser\u001b[39m = ammonite.$sess.cmd40$Helper$$anon$1@338f844e" ] }, "execution_count": 41, "metadata": {}, "output_type": "execute_result" } ], "source": [ "val traverser = new Traverser {\n", " override def apply(tree: Tree): Unit = tree match {\n", " case Pat.Var(name) =>\n", " println(s\"stop: $name\")\n", " case node =>\n", " println(s\"${node.productPrefix}: $node\")\n", " super.apply(node)\n", " }\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `super.apply(node)` call continues the recursion, so in this case we will\n", "recursively visit all nodes except children of `Pat.Var` nodes." ] }, { "cell_type": "code", "execution_count": 42, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Defn.Val: val x = 2\n", "stop: x\n", "Lit.Int: 2\n" ] } ], "source": [ "traverser(q\"val x = 2\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "There is no `.collect` equivalent for custom traversals. To collect a value,\n", "it's recommended to use `List.newBuilder[T]` for the type you are interested in\n", "and append values inside the `apply` method.\n", "\n", "## Transform trees\n", "\n", "Scalameta includes utilities to transform trees for simple and advanced\n", "use-cases.\n", "\n", "> Transformed trees do not preserve comments and formatting details when\n", "> pretty-printed. Look into [Scalafix](https://scalacenter.github.io/scalafix/)\n", "> if you need to implement fine-grained refactorings that preserve comments and\n", "> formatting details.\n", "\n", "### Simple transformations\n", "\n", "Use `.transform` to visit every tree node and transform interesting tree nodes." ] }, { "cell_type": "code", "execution_count": 43, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "val x = 42\n" ] } ], "source": [ "println(\n", " q\"val x = 2\".transform { case q\"2\" => q\"42\" }\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The contract of `.transform` is that it will recursively visit all tree nodes,\n", "including the transformed trees. Due to this behavior, a common mistake is to\n", "introduce infinite recursion in `.transform`" ] }, { "cell_type": "code", "execution_count": 44, "metadata": { "attributes": { "classes": [ "scala" ], "id": "" } }, "outputs": [ { "ename": "", "evalue": "", "output_type": "error", "traceback": [ "\u001b[31mjava.lang.StackOverflowError\u001b[39m\n scala.meta.transversers.Transformer.apply(\u001b[32mTransformer.scala\u001b[39m:\u001b[32m4\u001b[39m)\n scala.meta.transversers.Api$XtensionCollectionLikeUI$transformer$1$.apply(\u001b[32mApi.scala\u001b[39m:\u001b[32m10\u001b[39m)\n scala.meta.transversers.Transformer.apply(\u001b[32mTransformer.scala\u001b[39m:\u001b[32m4\u001b[39m)\n scala.meta.transversers.Api$XtensionCollectionLikeUI$transformer$1$.apply(\u001b[32mApi.scala\u001b[39m:\u001b[32m10\u001b[39m)\n scala.meta.transversers.Transformer.apply(\u001b[32mTransformer.scala\u001b[39m:\u001b[32m4\u001b[39m)\n scala.meta.transversers.Api$XtensionCollectionLikeUI$transformer$1$.apply(\u001b[32mApi.scala\u001b[39m:\u001b[32m10\u001b[39m)\n scala.meta.transversers.Transformer.apply(\u001b[32mTransformer.scala\u001b[39m:\u001b[32m4\u001b[39m)\n scala.meta.transversers.Api$XtensionCollectionLikeUI$transformer$1$.apply(\u001b[32mApi.scala\u001b[39m:\u001b[32m10\u001b[39m)\n scala.meta.transversers.Transformer.apply(\u001b[32mTransformer.scala\u001b[39m:\u001b[32m4\u001b[39m)\n scala.meta.transversers.Api$XtensionCollectionLikeUI$transformer$1$.apply(\u001b[32mApi.scala\u001b[39m:\u001b[32m10\u001b[39m)\n scala.meta.transversers.Transformer.apply(\u001b[32mTransformer.scala\u001b[39m:\u001b[32m4\u001b[39m)\n scala.meta.transversers.Api$XtensionCollectionLikeUI$transformer$1$.apply(\u001b[32mApi.scala\u001b[39m:\u001b[32m10\u001b[39m)\n scala.meta.transversers.Transformer.apply(\u001b[32mTransformer.scala\u001b[39m:\u001b[32m4\u001b[39m)\n scala.meta.transversers.Api$XtensionCollectionLikeUI$transformer$1$.apply(\u001b[32mApi.scala\u001b[39m:\u001b[32m10\u001b[39m)\n scala.meta.transversers.Transformer.apply(\u001b[32mTransformer.scala\u001b[39m:\u001b[32m4\u001b[39m)\n scala.meta.transversers.Api$XtensionCollectionLikeUI$transformer$1$.apply(\u001b[32mApi.scala\u001b[39m:\u001b[32m10\u001b[39m)\n scala.meta.transversers.Transformer.apply(\u001b[32mTransformer.scala\u001b[39m:\u001b[32m4\u001b[39m)\n scala.meta.transversers.Api$XtensionCollectionLikeUI$transformer$1$.apply(\u001b[32mApi.scala\u001b[39m:\u001b[32m10\u001b[39m)\n scala.meta.transversers.Transformer.apply(\u001b[32mTransformer.scala\u001b[39m:\u001b[32m4\u001b[39m)\n scala.meta.transversers.Api$XtensionCollectionLikeUI$transformer$1$.apply(\u001b[32mApi.scala\u001b[39m:\u001b[32m10\u001b[39m)\n scala.meta.transversers.Transformer.apply(\u001b[32mTransformer.scala\u001b[39m:\u001b[32m4\u001b[39m)\n scala.meta.transversers.Api$XtensionCollectionLikeUI$transformer$1$.apply(\u001b[32mApi.scala\u001b[39m:\u001b[32m10\u001b[39m)\n scala.meta.transversers.Transformer.apply(\u001b[32mTransformer.scala\u001b[39m:\u001b[32m4\u001b[39m)\n scala.meta.transversers.Api$XtensionCollectionLikeUI$transformer$1$.apply(\u001b[32mApi.scala\u001b[39m:\u001b[32m10\u001b[39m)\n scala.meta.transversers.Transformer.apply(\u001b[32mTransformer.scala\u001b[39m:\u001b[32m4\u001b[39m)\n scala.meta.transversers.Api$XtensionCollectionLikeUI$transformer$1$.apply(\u001b[32mApi.scala\u001b[39m:\u001b[32m10\u001b[39m)\n scala.meta.transversers.Transformer.apply(\u001b[32mTransformer.scala\u001b[39m:\u001b[32m4\u001b[39m)\n scala.meta.transversers.Api$XtensionCollectionLikeUI$transformer$1$.apply(\u001b[32mApi.scala\u001b[39m:\u001b[32m10\u001b[39m)\n scala.meta.transversers.Transformer.apply(\u001b[32mTransformer.scala\u001b[39m:\u001b[32m4\u001b[39m)\n scala.meta.transversers.Api$XtensionCollectionLikeUI$transformer$1$.apply(\u001b[32mApi.scala\u001b[39m:\u001b[32m10\u001b[39m)\n scala.meta.transversers.Transformer.apply(\u001b[32mTransformer.scala\u001b[39m:\u001b[32m4\u001b[39m)\n scala.meta.transversers.Api$XtensionCollectionLikeUI$transformer$1$.apply(\u001b[32mApi.scala\u001b[39m:\u001b[32m10\u001b[39m)\n scala.meta.transversers.Transformer.apply(\u001b[32mTransformer.scala\u001b[39m:\u001b[32m4\u001b[39m)\n scala.meta.transversers.Api$XtensionCollectionLikeUI$transformer$1$.apply(\u001b[32mApi.scala\u001b[39m:\u001b[32m10\u001b[39m)\n scala.meta.transversers.Transformer.apply(\u001b[32mTransformer.scala\u001b[39m:\u001b[32m4\u001b[39m)\n scala.meta.transversers.Api$XtensionCollectionLikeUI$transformer$1$.apply(\u001b[32mApi.scala\u001b[39m:\u001b[32m10\u001b[39m)\n scala.meta.transversers.Transformer.apply(\u001b[32mTransformer.scala\u001b[39m:\u001b[32m4\u001b[39m)\n scala.meta.transversers.Api$XtensionCollectionLikeUI$transformer$1$.apply(\u001b[32mApi.scala\u001b[39m:\u001b[32m10\u001b[39m)\n scala.meta.transversers.Transformer.apply(\u001b[32mTransformer.scala\u001b[39m:\u001b[32m4\u001b[39m)\n scala.meta.transversers.Api$XtensionCollectionLikeUI$transformer$1$.apply(\u001b[32mApi.scala\u001b[39m:\u001b[32m10\u001b[39m)\n scala.meta.transversers.Transformer.apply(\u001b[32mTransformer.scala\u001b[39m:\u001b[32m4\u001b[39m)\n scala.meta.transversers.Api$XtensionCollectionLikeUI$transformer$1$.apply(\u001b[32mApi.scala\u001b[39m:\u001b[32m10\u001b[39m)\n scala.meta.transversers.Transformer.apply(\u001b[32mTransformer.scala\u001b[39m:\u001b[32m4\u001b[39m)\n scala.meta.transversers.Api$XtensionCollectionLikeUI$transformer$1$.apply(\u001b[32mApi.scala\u001b[39m:\u001b[32m10\u001b[39m)\n scala.meta.transversers.Transformer.apply(\u001b[32mTransformer.scala\u001b[39m:\u001b[32m4\u001b[39m)\n scala.meta.transversers.Api$XtensionCollectionLikeUI$transformer$1$.apply(\u001b[32mApi.scala\u001b[39m:\u001b[32m10\u001b[39m)\n scala.meta.transversers.Transformer.apply(\u001b[32mTransformer.scala\u001b[39m:\u001b[32m4\u001b[39m)\n scala.meta.transversers.Api$XtensionCollectionLikeUI$transformer$1$.apply(\u001b[32mApi.scala\u001b[39m:\u001b[32m10\u001b[39m)\n scala.meta.transversers.Transformer.apply(\u001b[32mTransformer.scala\u001b[39m:\u001b[32m4\u001b[39m)\n scala.meta.transversers.Api$XtensionCollectionLikeUI$transformer$1$.apply(\u001b[32mApi.scala\u001b[39m:\u001b[32m10\u001b[39m)\n scala.meta.transversers.Transformer.apply(\u001b[32mTransformer.scala\u001b[39m:\u001b[32m4\u001b[39m)\n scala.meta.transversers.Api$XtensionCollectionLikeUI$transformer$1$.apply(\u001b[32mApi.scala\u001b[39m:\u001b[32m10\u001b[39m)\n scala.meta.transversers.Transformer.apply(\u001b[32mTransformer.scala\u001b[39m:\u001b[32m4\u001b[39m)\n scala.meta.transversers.Api$XtensionCollectionLikeUI$transformer$1$.apply(\u001b[32mApi.scala\u001b[39m:\u001b[32m10\u001b[39m)\n scala.meta.transversers.Transformer.apply(\u001b[32mTransformer.scala\u001b[39m:\u001b[32m4\u001b[39m)\n scala.meta.transversers.Api$XtensionCollectionLikeUI$transformer$1$.apply(\u001b[32mApi.scala\u001b[39m:\u001b[32m10\u001b[39m)\n scala.meta.transversers.Transformer.apply(\u001b[32mTransformer.scala\u001b[39m:\u001b[32m4\u001b[39m)\n scala.meta.transversers.Api$XtensionCollectionLikeUI$transformer$1$.apply(\u001b[32mApi.scala\u001b[39m:\u001b[32m10\u001b[39m)\n scala.meta.transversers.Transformer.apply(\u001b[32mTransformer.scala\u001b[39m:\u001b[32m4\u001b[39m)\n scala.meta.transversers.Api$XtensionCollectionLikeUI$transformer$1$.apply(\u001b[32mApi.scala\u001b[39m:\u001b[32m10\u001b[39m)\n scala.meta.transversers.Transformer.apply(\u001b[32mTransformer.scala\u001b[39m:\u001b[32m4\u001b[39m)\n scala.meta.transversers.Api$XtensionCollectionLikeUI$transformer$1$.apply(\u001b[32mApi.scala\u001b[39m:\u001b[32m10\u001b[39m)\n scala.meta.transversers.Transformer.apply(\u001b[32mTransformer.scala\u001b[39m:\u001b[32m4\u001b[39m)\n scala.meta.transversers.Api$XtensionCollectionLikeUI$transformer$1$.apply(\u001b[32mApi.scala\u001b[39m:\u001b[32m10\u001b[39m)\n scala.meta.transversers.Transformer.apply(\u001b[32mTransformer.scala\u001b[39m:\u001b[32m4\u001b[39m)\n scala.meta.transversers.Api$XtensionCollectionLikeUI$transformer$1$.apply(\u001b[32mApi.scala\u001b[39m:\u001b[32m10\u001b[39m)\n scala.meta.transversers.Transformer.apply(\u001b[32mTransformer.scala\u001b[39m:\u001b[32m4\u001b[39m)\n scala.meta.transversers.Api$XtensionCollectionLikeUI$transformer$1$.apply(\u001b[32mApi.scala\u001b[39m:\u001b[32m10\u001b[39m)\n scala.meta.transversers.Transformer.apply(\u001b[32mTransformer.scala\u001b[39m:\u001b[32m4\u001b[39m)\n scala.meta.transversers.Api$XtensionCollectionLikeUI$transformer$1$.apply(\u001b[32mApi.scala\u001b[39m:\u001b[32m10\u001b[39m)\n scala.meta.transversers.Transformer.apply(\u001b[32mTransformer.scala\u001b[39m:\u001b[32m4\u001b[39m)\n scala.meta.transversers.Api$XtensionCollectionLikeUI$transformer$1$.apply(\u001b[32mApi.scala\u001b[39m:\u001b[32m10\u001b[39m)\n scala.meta.transversers.Transformer.apply(\u001b[32mTransformer.scala\u001b[39m:\u001b[32m4\u001b[39m)\n scala.meta.transversers.Api$XtensionCollectionLikeUI$transformer$1$.apply(\u001b[32mApi.scala\u001b[39m:\u001b[32m10\u001b[39m)\n scala.meta.transversers.Transformer.apply(\u001b[32mTransformer.scala\u001b[39m:\u001b[32m4\u001b[39m)\n scala.meta.transversers.Api$XtensionCollectionLikeUI$transformer$1$.apply(\u001b[32mApi.scala\u001b[39m:\u001b[32m10\u001b[39m)\n scala.meta.transversers.Transformer.apply(\u001b[32mTransformer.scala\u001b[39m:\u001b[32m4\u001b[39m)\n scala.meta.transversers.Api$XtensionCollectionLikeUI$transformer$1$.apply(\u001b[32mApi.scala\u001b[39m:\u001b[32m10\u001b[39m)\n scala.meta.transversers.Transformer.apply(\u001b[32mTransformer.scala\u001b[39m:\u001b[32m4\u001b[39m)\n scala.meta.transversers.Api$XtensionCollectionLikeUI$transformer$1$.apply(\u001b[32mApi.scala\u001b[39m:\u001b[32m10\u001b[39m)\n scala.meta.transversers.Transformer.apply(\u001b[32mTransformer.scala\u001b[39m:\u001b[32m4\u001b[39m)\n scala.meta.transversers.Api$XtensionCollectionLikeUI$transformer$1$.apply(\u001b[32mApi.scala\u001b[39m:\u001b[32m10\u001b[39m)\n scala.meta.transversers.Transformer.apply(\u001b[32mTransformer.scala\u001b[39m:\u001b[32m4\u001b[39m)\n scala.meta.transversers.Api$XtensionCollectionLikeUI$transformer$1$.apply(\u001b[32mApi.scala\u001b[39m:\u001b[32m10\u001b[39m)\n scala.meta.transversers.Transformer.apply(\u001b[32mTransformer.scala\u001b[39m:\u001b[32m4\u001b[39m)\n scala.meta.transversers.Api$XtensionCollectionLikeUI$transformer$1$.apply(\u001b[32mApi.scala\u001b[39m:\u001b[32m10\u001b[39m)\n scala.meta.transversers.Transformer.apply(\u001b[32mTransformer.scala\u001b[39m:\u001b[32m4\u001b[39m)\n scala.meta.transversers.Api$XtensionCollectionLikeUI$transformer$1$.apply(\u001b[32mApi.scala\u001b[39m:\u001b[32m10\u001b[39m)\n scala.meta.transversers.Transformer.apply(\u001b[32mTransformer.scala\u001b[39m:\u001b[32m4\u001b[39m)\n scala.meta.transversers.Api$XtensionCollectionLikeUI$transformer$1$.apply(\u001b[32mApi.scala\u001b[39m:\u001b[32m10\u001b[39m)\n scala.meta.transversers.Transformer.apply(\u001b[32mTransformer.scala\u001b[39m:\u001b[32m4\u001b[39m)\n scala.meta.transversers.Api$XtensionCollectionLikeUI$transformer$1$.apply(\u001b[32mApi.scala\u001b[39m:\u001b[32m10\u001b[39m)\n scala.meta.transversers.Transformer.apply(\u001b[32mTransformer.scala\u001b[39m:\u001b[32m4\u001b[39m)\n scala.meta.transversers.Api$XtensionCollectionLikeUI$transformer$1$.apply(\u001b[32mApi.scala\u001b[39m:\u001b[32m10\u001b[39m)\n scala.meta.transversers.Transformer.apply(\u001b[32mTransformer.scala\u001b[39m:\u001b[32m4\u001b[39m)\n scala.meta.transversers.Api$XtensionCollectionLikeUI$transformer$1$.apply(\u001b[32mApi.scala\u001b[39m:\u001b[32m10\u001b[39m)\n scala.meta.transversers.Api$XtensionCollectionLikeUI.transform(\u001b[32mApi.scala\u001b[39m:\u001b[32m13\u001b[39m)\n ammonite.$sess.cmd43$Helper.(\u001b[32mcmd43.sc\u001b[39m:\u001b[32m1\u001b[39m)\n ammonite.$sess.cmd43$.(\u001b[32mcmd43.sc\u001b[39m:\u001b[32m7\u001b[39m)\n ammonite.$sess.cmd43$.(\u001b[32mcmd43.sc\u001b[39m:\u001b[32m-1\u001b[39m)" ] } ], "source": [ "q\"a + b\".transform {\n", " case name @ Term.Name(\"b\") => q\"function($name)\"\n", "}.toString\n", "// [error] java.lang.StackOverflowError\n", "// at scala.meta.transversers.Api$XtensionCollectionLikeUI$transformer$2$.apply(Api.scala:10)\n", "// at scala.meta.transversers.Transformer.apply(Transformer.scala:4)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The best solution to fix this problem is to implement a custom transformer to\n", "gain fine-grained control over the recursion.\n", "\n", "### Custom transformations\n", "\n", "Extend `Transformer` if you need to implement a custom tree transformation" ] }, { "cell_type": "code", "execution_count": 45, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\u001b[36mtransformer\u001b[39m: \u001b[32mTransformer\u001b[39m = ammonite.$sess.cmd44$Helper$$anon$1@1ec9a4b4" ] }, "execution_count": 45, "metadata": {}, "output_type": "execute_result" } ], "source": [ "val transformer = new Transformer {\n", " override def apply(tree: Tree): Tree = tree match {\n", " case name @ Term.Name(\"b\") => q\"function($name)\"\n", " case node => super.apply(node)\n", " }\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "By avoiding the call to `super.transform` in the first case, we prevent a stack\n", "overflow." ] }, { "cell_type": "code", "execution_count": 46, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "a + function(b)\n" ] } ], "source": [ "println(\n", " transformer(q\"a + b\")\n", ")" ] } ], "metadata": { "kernelspec": { "display_name": "Scala (2.13)", "language": "scala", "name": "scala213" }, "language_info": { "codemirror_mode": "text/x-scala", "file_extension": ".scala", "mimetype": "text/x-scala", "name": "scala", "nbconvert_exporter": "script", "version": "2.13.1" } }, "nbformat": 4, "nbformat_minor": 4 }