by

Where communities thrive


  • Join over 1.5M+ people
  • Join over 100K+ communities
  • Free without limits
  • Create your own community
People
Repo info
Activity
    Stefan Wachter
    @swachter
    1. https://github.com/code-star/scala-tsi code generation with implicits and macros based
    Stefan Wachter
    @swachter
    I found the approach of scala-tsi particularly interesting. A code generator class is created during the build that is run during the build and outputs the typescript. That allows to use the standard implicit machinery to derive the information that is necessary for typescript output.
    Sören Brunk
    @sbrunk
    @swachter there’s also https://github.com/davegurnell/bridges but I think it’s restricted to ADTs
    Stefan Wachter
    @swachter
    I can image the following approach to be a solid solution: Use ScalaMeta to translate Scala sources into code generators. The structure of code generators corresponds to the structure of the source. For example if a source file contains a method definition that should be exported then the code generator contains a corresponding call to a exportDef(...) method. The exportDef method is a varargs method where each argument is converted by an implicit conversion into the information that is necessary to output the typescript signature.
    Ólafur Páll Geirsson
    @olafurpg
    @swachter one relevant thing I did not mention before, if you want to keep docstrings then you probably want to use scalameta
    scala-reflect does not offer a way to read docstrings
    we have an org.scalameta:mtags module that supports rendering scala/java docstrings into markdown given a SemanticDB symbol. We use mtags in Metals to power goto definition indexes and displaying docstrings in completion results + type at point
    I still think it's best to base the TypeScript code generation on a symbol table like scala-reflect because that gives you fully resolved named
    when you parse a source file with scalameta you only see the syntax, not the fully qualified names
    Ólafur Páll Geirsson
    @olafurpg
    @swachter here is an example how you can use mtags to query docstrings based on a semanticdb symbol https://gist.github.com/olafurpg/23c9628481b9f008bdc581164938b42c
    we also have some ways to convert scala-reflect symbols into semanticdb symbols, which allows you to implement code generation based on a symbol table (instead of parsed syntax trees) but query syntax information like docstrings
    Stefan Wachter
    @swachter
    metacp is compiled for Scala 2.11 and 2.12 but not for 2.13. However, it seems that metacp for Scala 2.12 can process the scala-library-2.13.1.jar. Is this expected behaviour?
    Kalin-Rudnicki
    @Kalin-Rudnicki

    Is it possible to do this with scalameta?

    
    class MyMonad[T](value: T) {
    
      def map: Nothing = ???
      def flatMap: Nothing = ???
    
      // .....
    
      // Automatically generated code?
    
    }
    
    object MyMonadThing {
    
      implicit def tToMyMonadThingT[T](value: T): MyMonad[T] =
        new MyMonad[T](value)
    
    }
    
    object Usage {
    
      import MyMonadThing._
    
      val res0: Int = 5 + 5
      val res1: MyMonad[Int] = 5 + 5
      val res2: MyMonad[Int] = res1 + res0
    
    }

    Where it would be able to figure out that:

    res0 + res1
    // should really be:
    res1.map(_ + res0)
    Dmytro Mitin
    @DmytroMitin
    @Kalin-Rudnicki it seems you don't need anything advanced, you can just define extension method
    Oleksandr Soldatov
    @captify-osoldatov
    Hey there, is there are any example how I can use scalameta to generate boilerplate code ? e.g. add specific method to each child of parent class
    Dmytro Mitin
    @DmytroMitin

    @captify-osoldatov
    https://stackoverflow.com/questions/61796570/scala-conditional-compilation/
    https://stackoverflow.com/questions/35338239/macro-annotation-to-override-tostring-of-scala-function/
    https://stackoverflow.com/questions/58562229/how-to-merge-multiple-imports-in-scala/
    Define the following project/Transformer.scala

    import scala.meta._
    
    object Transformer {
      def transform(input: String): String = {
        transform(input.parse[Source].get).syntax
      }
    
      def transform(tree: Tree): Tree = {
        tree.transform {
          case q"""..$mods class $tname[..$tparams] ..$ctorMods (...$paramss) extends ${
            template"{ ..$stats } with ..$inits { $self => ..$stats1 }"
          }""" if inits.collect { case t@init"SomeClass" => t }.nonEmpty =>
            q"""..$mods class $tname[..$tparams] ..$ctorMods (...$paramss) extends ${
              template"""{ ..$stats } with ..$inits { $self =>
                def foo(): Unit = ()
                ..$stats1
              }"""}"""
    
          case q"""..$mods object $ename extends ${
            template"{ ..$stats } with ..$inits { $self => ..$stats1 }"
          }""" if inits.collect { case t@init"SomeClass" => t }.nonEmpty =>
            q"""..$mods object $ename extends ${
              template"""{ ..$stats } with ..$inits { $self =>
                def foo(): Unit = ()
                ..$stats1
              }"""
            }"""
        }
      }
    }

    This adds def foo(): Unit = () to every class or object extending SomeClass.

    Oleksandr Soldatov
    @captify-osoldatov
    @DmytroMitin Thanks! will check out a bit later
    Yumy
    @Yumy

    Hi! I'm completely new to scalamet and trying to transform or traverce a code source file to get the class/trait/def structure in xml.
    I've tried the both transform and traverse but can't get what I want - the set of nested <node> tags corresponding to the structure.

    Here is one of my attempt which partly works - produces the file containing the definition lines but since I can't call this recursively I can't get the nested <node> </node>tags. I've also tried

    def customTransformer1(filename:String) = {

    val file = new File(filename)
    val bw = new BufferedWriter(new FileWriter(file))
    
    
    val x = this.exampleTree2.transform {
    
      case source"..$stats" => {bw.write("source" +"\n")
                    source"..$stats"
                             }
      case q"package $eref { ..$stats }" => {bw.write("Pkg "+eref+"\n")
                          q"package $eref { ..$stats }"
                            }
      case q"..$mods class $tname[..$tparams] ..$ctorMods (...$paramss) extends $template" => {
        bw.write("class "+tname+"\n")
        q"..$mods class $tname[..$tparams] ..$ctorMods (...$paramss) extends $template"
      }
      case q"..$mods trait $tname[..$tparams] extends $template" => {
        bw.write("trait "+tname+"\n")
        q"..$mods trait $tname[..$tparams] extends $template"
      }
      case q"..$mods def $ename[..$tparams](...$paramss): $tpeopt = $expr" =>{
        bw.write("def "+ename+paramss+"\n")
        q"..$mods def $ename[..$tparams](...$paramss): $tpeopt = $expr"
      }
    
    }.toString()
    
    
    bw.close()

    }

    I've also tried to write custom transform/traverse with
    super.apply(node)
    but can't get it work

    Any help would be higly appretiated.
    Thank you in advance.

    Ólafur Páll Geirsson
    @olafurpg
    @Yumy have you tried writing a custom Traverser? https://scalameta.org/docs/trees/guide.html#custom-traversals
    Yumy
    @Yumy
    @olafurpg yes
    Ólafur Páll Geirsson
    @olafurpg
    can you elaborate why that didn't work?
    Yumy
    @Yumy

    Thank you for the answer! Trying to reproduce my problem with Traverser I occassionally got the result which I wanted. Here is my code and now there are just some little questions (below the code)

    def customTravers2(filename:String) = {

    val file = new File(filename)
    val bw = new BufferedWriter(new FileWriter(file))
    
    val traverser = new Traverser {
    
      override def apply(tree: Tree): Unit = {
    
        tree match {
    
          case source"..$stats" => {
            bw.write("<node>"+ "\n")
            bw.write(s"source " + "\n")
            super.apply(stats(0))
            bw.write("</node>"+ "\n")
          }
          case q"package $eref { ..$stats }" => {
            bw.write("<node>"+ "\n")
            bw.write(s"package $eref" + "\n")
            super.apply(stats(0))
            bw.write("</node>"+ "\n")
          }
          case q"..$mods class $tname[..$tparams] ..$ctorMods (...$paramss) extends $template" => {
            bw.write("<node>"+ "\n")
            bw.write(s"class $tname $paramss" + "\n")
            super.apply(template)
            bw.write("</node>"+ "\n")
          }
    
          case q"..$mods object $ename extends $template" => {
            bw.write("<node>"+ "\n")
            bw.write(s"object $ename" + "\n")
            super.apply(template)
            bw.write("</node>"+ "\n")
          }
          case q"..$mods trait $tname[..$tparams] extends $template" => {
            bw.write("<node>"+ "\n")
            bw.write( s"trait $tname" + "\n")
            super.apply(template)
            bw.write("</node>"+ "\n")
          }
    
    
          case q"..$mods def $ename[..$tparams](...$paramss): $tpeopt = $expr" => {
            bw.write("<node>"+ "\n")
            bw.write( s"def $ename $paramss" + "\n")
    
             super.apply(expr)
            bw.write("</node>"+ "\n")
          }
    
        case  node => {}
        }
      }
    }
    traverser(exampleTree2)
    bw.close()

    }

    This code produces (almost what I want)

    <node>
    source
    <node>
    class User List(List(id: String="", email: String, firstName: String, lastName: String, password:String, agreem:Boolean=false, activationStatus:Int=0, acode:String=""))
    <node>
    def activate List(List(acodeFromRequest: String))
    </node>
    <node>
    def update List(List(userNewData: UserEditData))
    </node>
    </node>
    <node>
    object User
    <node>
    def apply List(List(email: String, firstName: String, lastName: String, password:String, agreem:Boolean, activationStatus:Int, acode:String))
    </node>
    <node>
    def findAll List()
    </node>
    <node>
    def findById List(List(id:Int))
    </node>
    <node>
    def findByACode List(List(acode:String))
    </node>
    <node>
    def findByEmail List(List(email:String))
    </node>
    <node>
    def generateACode List(List(email:String, lastName:String))
    </node>
    <node>
    def emailServerIsValid List(List(s: String))
    </node>
    <node>
    def charsAreAllowedInNames List(List(s: String))
    </node>
    <node>
    def encryptPassword List(List(pwd:String))
    </node>
    <node>
    def passwordIsCorrect List(List(email:String, password: String))
    </node>
    </node>
    </node>

    So here are the questions:

    1) The code line " case node => {}" produces nothing but this doesn't work whithout it. Why do we need such the line?
    2) Am I right using " super.apply(stats(0))" specifically stats(0) to get nested nodes for some nodes. It looks not well unified....
    3) I get list of args as "List(List(userNewData: UserEditData))". How can I correctly get just the inner part : "(userNewData: UserEditData)"?

    Yumy
    @Yumy
    I've found the solution for the third question (need to use q instead of s before printed strings. I would be happy if you can answer 1) and 2)
    Dmytro Mitin
    @DmytroMitin
    @Yumy without 1) you can have MatchError.
    Rob Norris
    @tpolecat
    Hi, this is probably an FAQ but I have failed to find an answer. I am generating code using scala.meta._ and would like to add comments so the scaladoc for the generated code will be useful. My current plan is to toString the trees and do regex search/replace on the string to insert comments as a second pass, which seems awful. Hoping I'm missing something obvious.
    I know scalafmt understands comments and I thought it was the same AST, so I am confuse.
    It might also be nice, while I'm here, to know how I can format a scalameta tree using scalafmt.
    And it also seems that a quasiquote can't begin with an import statement so I'm using strings for that bit. Probably missing something there as well.
    I think that's all my questions ;-)
    Ólafur Páll Geirsson
    @olafurpg
    @tpolecat the short answer is it's not possible to attach comments to tree nodes and you probably don't want to use scala.meta.Tree for code generation anyways
    I recommend using https://github.com/typelevel/paiges instead
    there exists a longer answer which involved building a repo from source that solves most of the issues with generating source code with scalameta trees but there are no plans to polish that integration and publish it
    Rob Norris
    @tpolecat
    I don't really care that much about formatting, it's just nice to not have to munge strings.
    Thanks then, I will carry on.
    Ólafur Páll Geirsson
    @olafurpg
    scalameta trees gives you some compile-time safety that you're producing syntactically valid programs, but you need to compile the generated code anyways to know if the generated code makes sense semantically
    it's easier to munge strings in this particular use-case IMO
    and paiges is just a pleasure to use in my experience
    Rob Norris
    @tpolecat
    I rewrote it using plain strings and it’s a lot simpler so yeah I’ll just go with that. Thanks.
    Ólafur Páll Geirsson
    @olafurpg
    That’s my experience as well, sometimes plain old strings are fine. Paiges is seriously nice though, you should try it some day
    Kalin-Rudnicki
    @Kalin-Rudnicki
    @DmytroMitin Sorry for the late reply. I dont think that is what I am looking for. Im not looking to just randomly add + to 1 thing. Im saying that for anything, it could look inside whatever it is wrapped in, and enact map/flatMap on it. +, -, firstName, toCharArray, anything...
    Dmytro Mitin
    @DmytroMitin
    @Kalin-Rudnicki I see. Then generating methods with Scalameta (or with scala-reflect macro annotations) is not an option because MyMonad[Int] and MyMonad[String] should have different sets of methods (in Scala/Java generic classes are compiled once on contrary to templates in C++ compiled for every type parameter). So it seems you're looking for scala.Dynamic
      class MyMonad[T](val value: T) extends Dynamic {
        // ...
        def applyDynamic(method: String)(args: Any*): Any = macro MyMonadMacro.applyDynamicImpl
      }
    
      object MyMonadMacro {
        def applyDynamicImpl(c: whitebox.Context)(method: c.Tree)(args: c.Tree*): c.Tree = {
          import c.universe._
          val q"${methodName: String}" = method
          q"${c.prefix}.value.${TermName(methodName).encodedName.toTermName}(..$args)"
        }
      }
    
    import Predef.{any2stringadd => _}
    import MyMonadThing._
    val res0: Int = 5 + 5
    val res1: MyMonad[Int] = 5 + 5
    val res2: MyMonad[Int] = res1 + res0 // compiles
    Kalin-Rudnicki
    @Kalin-Rudnicki
    @DmytroMitin This looks really cool, it doesnt quite seem to want to compile though. Is there any way for it to tell you what the actual type should be, or are you stuck with Any here?
    Dmytro Mitin
    @DmytroMitin
    @Kalin-Rudnicki It should compile. If it doesn't compile for you, compile error can be helpful. args should be Any because methods of T can accept arguments of arbitrary types. Any in return type is not actually Any but some subtype of Any (because the macro is whitebox). res1 + res0 has type Int and not Any(that's why implicit conversion Int => MyMonad[Int] works, otherwise if the macro were blackbox then for Any it wouldn't because there is no implicit conversion Any => MyMonad[Int]).
    Kalin-Rudnicki
    @Kalin-Rudnicki
    Wow @DmytroMitin , this is so cool. I am closer than I have ever been to getting this to work. There is only 1 problem left that I am facing...
    
    import klib.dynamic._
    import klib.fp.ops._
    
    object Test {
    
      case class Person(var firstName: String, var lastName: String, var age: Int) {
    
        def asStr(f: Person => String): String =
          f(this)
    
      }
    
      def main(args: Array[String]): Unit = {
    
        val person: ??[Person] = Person("First-1", "Last-1", 1).lift[??]
    
        println(person.firstName)
        println(person.lastName)
        println(person.age)
    
        person.firstName = "First"
        person.lastName = "Last"
        person.age = 2
    
        println(person)
        println(person.asStr((p: Person) => s"${p.lastName}, ${p.firstName}"))
    
        // Everything above this line is working how I would hope
    
        val _4: ??[Int] = person.age + person.age
    
      }
    
    }
    I have it working where person.firstName translates to person.map(_.firstName), but what I would also like is:
    person.age + person.age
    // translates to:
    person.flatMap { p1 =>
      person.map { p2 =>
        p1 + p2
      }
    }
    Is there a way I can do type checking of the arguments, so that I know if I need to wrap extra flatMaps around it?
    Dmytro Mitin
    @DmytroMitin
    @Kalin-Rudnicki The difference is that Person doesn't have explicit method firstName so compiler starts to look for it via implicits and dynamics. But Int already has explicit method + so compiler won't look for it via implcits and dynamics. person.age + person.age is just a sum of two Ints which don't remember that they were person.age. So now you want to work with them as ASTs. Rewriting one tree to another wouldn't be difficult with Scalameta but detecting when such rewriting should be applied can be harder (person.age + person.age should be rewritten but 1 + 1 shouldn't). I guess one would need a compiler plugin.
    Ólafur Páll Geirsson
    @olafurpg
    @DmytroMitin @Kalin-Rudnicki it sounds like you would have an easier time implementing this with the compiler api instead of Scalameta
    Kalin-Rudnicki
    @Kalin-Rudnicki
    @olafurpg Do you know a good resource for this?
    Ólafur Páll Geirsson
    @olafurpg
    @Kalin-Rudnicki https://docs.scala-lang.org/overviews/plugins/index.html or any resources on scala macros