0

I'm developing a library and want to showcase some code in a small website. The code-snippets should be sourced directly from the same project, i.e. the code needs to be self-referencing.

I want to write a macro that, given a file or a classname returns a string with the file contents. How can I achieve this?

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
Felix
  • 8,385
  • 10
  • 40
  • 59
  • Suppose your macro is `inline def getContent(filePath: String): String = ${getContentImpl('className)}`. Is `filePath` a compile-time string (like in `getContent("path/to/file")`) or runtime string (like in `val str = "path/to/file"; getContent(str)`)? – Dmytro Mitin Feb 18 '23 at 21:59
  • Why do you want this to be a macro? Why can't it be an ordinary method? – Dmytro Mitin Feb 18 '23 at 22:01
  • The files don't exist at runtime. They are the the source of the running application. – Felix Feb 19 '23 at 06:41
  • I would also accept giving a classname or type as argument and returning the source of the class/object /trait or whatever implemented that type – Felix Feb 19 '23 at 06:43
  • *"The files don't exist at runtime."* Why? Are you deleting the sources after compilation? – Dmytro Mitin Feb 19 '23 at 09:00
  • Why not to run scala compiler at runtime and compile all necessary code snippets? – Dmytro Mitin Feb 19 '23 at 10:38
  • 1
    The files exist at runtime, but the application loses track of them. Running the compiler at runtime would cause so many problems I don't even know where to start. Your answer below seems to be what I need - I'll return once I have the chance to test it out :D Thanks! – Felix Feb 20 '23 at 07:16

1 Answers1

1

I would also accept giving a classname or type as argument

Are you looking for such a macro?

import scala.quoted.*

inline def getContent[A]: String = ${getContentImpl[A]}

def getContentImpl[A: Type](using Quotes): Expr[String] =
  import quotes.reflect.*

     // for the source specifically of A
  // val str = TypeRepr.of[A].typeSymbol.tree.pos.sourceCode.getOrElse(
  //   report.errorAndAbort("no source code")
  // )

     // for the whole source file containing the definition of A
  val str = TypeRepr.of[A].typeSymbol.pos.getOrElse(
    report.errorAndAbort("no symbol position")
  ).sourceFile.content.getOrElse(
    report.errorAndAbort("no source-file content")
  )

  Expr(str)
// in a different file

getContent[A]

//import Macros.getContent
//
//object Main extends App {
//  class A:
//    def foo = 1
//
//  println(
//    getContent[A]
//  )
//}

How to convert scala code block to string? (Scala 2)

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
  • That's really good, thank you! – Felix Feb 20 '23 at 08:18
  • I have an issue -- sometimes it fails and the position is printed as "?", any ideas? – Felix Feb 20 '23 at 14:24
  • @Felix No. Weird. If the source is not available it shouldn't compile, not return `?`. Try to create reproduction – Dmytro Mitin Feb 20 '23 at 14:51
  • 1
    https://gist.github.com/hejfelix/24a59869231cf435a03370bd03d361ec first time I run it in sbt, it works as intended and prints: " class Foo[F[_]] { } " next time, if I insert a linebreak or any benign change, it prints an empty string with no error. report.info reveals that the position is "?" and the sourceFile is "NoFile" smells like a caching error. – Felix Feb 20 '23 at 18:01
  • Cleaning and recompiling seems to fix it, but then it quickly breaks again. – Felix Feb 20 '23 at 20:47
  • 1
    I have a repository to reproduce the issue here: https://github.com/hejfelix/macro-repro – Felix Feb 21 '23 at 10:41
  • @Felix Clean compilation is a good practice while working with generated code. Behavior in Scala 2 is the same https://gist.github.com/DmytroMitin/344f2d1410b4f285bc95b8299f9096aa I suspect this is not a bug but how sbt + [zinc](https://github.com/sbt/zinc/) manage [incremental compilation](https://www.scala-sbt.org/1.x/docs/Understanding-Recompilation.html). Upon clean compilation sbt writes `compiling 3 Scala sources`, upon recompilation after modification of `SourceMacro` it writes `compiling 1 Scala source`, `compiling 1 Scala source`. I guess this makes impact on a position. – Dmytro Mitin Feb 21 '23 at 17:34
  • @Felix Why is clean compilation an issue for your use case? – Dmytro Mitin Feb 21 '23 at 17:35
  • 1
    I guess you're right. It is just annoying while debugging/developing. The final artefact comes from CI which currently does a clean compile so it should be OK. Thanks for looking into this, I have re-accepted – Felix Feb 22 '23 at 05:23