1

If I have the following type

Defn.Var(mods, pats, decltpe, rhs) 

in scalameta it might happen that decltype is set to None for a variable like this:

var x = 10

I still want to know the exact type of the variable x which Scala has inferred without checking the type of the assignment expression myself. I know that I can just get the information that 10 is an Int literal but for more complex expressions it might be helpful to have some helper function for the type. Is there any function in scalameta which gives you the inferred type?

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
Baradé
  • 1,290
  • 1
  • 15
  • 35

1 Answers1

2

Scalameta

libraryDependencies += "org.scalameta" %% "scalameta" % "4.2.0"

mostly works with source code (parses it to trees, transforms trees) before its compilation i.e. when there can't be any information about symbols and types. To get information about symbols and types one should start compiler (one of).

This is exactly what SemanticDB is for. If you switch on semanticdb-scalac compiler plugin

addCompilerPlugin("org.scalameta" % "semanticdb-scalac" % "4.1.0" cross CrossVersion.full)
scalacOptions ++= Seq(
  "-Yrangepos",
  "-P:semanticdb:synthetics:on",
)

then upon compilation it will generate .semanticdb files near to .class files. Information about symbols and types will be there. These files can be parsed with semanticdb

libraryDependencies += "org.scalameta" %% "semanticdb" % "4.1.0"

For example if you have App1.scala

object App1 {
  var x = 10
}

then

import java.nio.file.Paths
import scala.meta.internal.semanticdb.Locator
Locator(
  Paths.get("./target/scala-2.12/classes/META-INF/semanticdb/src/main/scala/App1.scala.semanticdb")
)((path, textDocuments) =>
  println(textDocuments)
)

produces

TextDocuments(Vector(TextDocument(SEMANTICDB4,src/main/scala/App1.scala,,29E9BFD566BEFD436FBE59679524E53D,SCALA,Vector(SymbolInformation(_empty_/App1.`x_=`().,SCALA,METHOD,2048,x_=,MethodSignature(Some(Scope(Vector(),Vector())),Vector(Scope(Vector(_empty_/App1.`x_=`().(x$1)),Vector())),TypeRef(Empty,scala/Unit#,Vector())),Vector(),PublicAccess()), SymbolInformation(_empty_/App1.,SCALA,OBJECT,8,App1,ClassSignature(Some(Scope(Vector(),Vector())),Vector(TypeRef(Empty,scala/AnyRef#,Vector())),Empty,Some(Scope(Vector(_empty_/App1.x()., _empty_/App1.`x_=`().),Vector()))),Vector(),PublicAccess()), SymbolInformation(_empty_/App1.`x_=`().(x$1),SCALA,PARAMETER,0,x$1,ValueSignature(TypeRef(Empty,scala/Int#,Vector())),Vector(),Empty), SymbolInformation(_empty_/App1.x().,SCALA,METHOD,2048,x,MethodSignature(Some(Scope(Vector(),Vector())),Vector(),TypeRef(Empty,scala/Int#,Vector())),Vector(),PublicAccess())),Vector(SymbolOccurrence(Some(Range(1,6,1,7)),_empty_/App1.x().,DEFINITION), SymbolOccurrence(Some(Range(0,7,0,11)),_empty_/App1.,DEFINITION)),Vector(),Vector())))

You can pretty-print this file with Metap

import scala.meta.cli.Metap
Metap.main(Array("target/scala-2.12/classes/META-INF/semanticdb/src/main/scala/App1.scala.semanticdb"))

Output

src/main/scala/App1.scala
-------------------------

Summary:
Schema => SemanticDB v4
Uri => src/main/scala/App1.scala
Text => empty
Language => Scala
Symbols => 4 entries
Occurrences => 2 entries

Symbols:
_empty_/App1. => final object App1 extends AnyRef { +2 decls }
_empty_/App1.`x_=`(). => var method x_=(x$1: Int): Unit
_empty_/App1.`x_=`().(x$1) => param x$1: Int
_empty_/App1.x(). => var method x: Int

Occurrences:
[0:7..0:11) <= _empty_/App1.
[1:6..1:7) <= _empty_/App1.x().

And

import scala.meta.internal.semanticdb.TypeRef
import scala.meta.internal.semanticdb.SignatureMessage.SealedValue.{ClassSignature, MethodSignature, TypeSignature, ValueSignature, Empty}

Locator(
  Paths.get("./target/scala-2.12/classes/META-INF/semanticdb/src/main/scala/App1.scala.semanticdb")
)((path, textDocuments) =>
  for {
    document <- textDocuments.documents
    symbol <- document.symbols
  } println(s"symbol=${symbol.displayName}, ${symbol.signature.asMessage.sealedValue match {
    case v: ValueSignature => s"ValueSignature, type=${v.value.tpe match { case t: TypeRef => t.symbol}}"
    case m: MethodSignature => s"MethodSignature, returnType=${m.value.returnType match { case t: TypeRef => t.symbol}}"
    case c: ClassSignature => "ClassSignature"
    case t: TypeSignature => "TypeSignature"
    case Empty => "Empty"
  }}")
)

produces

symbol=x_=, MethodSignature, returnType=scala/Unit#
symbol=App1, ClassSignature
symbol=x$1, ValueSignature, type=scala/Int#
symbol=x, MethodSignature, returnType=scala/Int#

Scheme is here.

Also you can try Scalafix

sbt new scalacenter/scalafix.g8 --repo="scalafixdemo"
cd scalafix
sbt ~tests/test

If you write in input/src/main/scala/fix/Scalafixdemo.scala

package fix

object Scalafixdemo {
  var x = 10
}

and in rules/src/main/scala/fix/Scalafixdemo.scala

package fix

import scalafix.v1._
import scala.meta._

class Scalafixdemo extends SemanticRule("Scalafixdemo") {

  override def fix(implicit doc: SemanticDocument): Patch = {
//    println("Tree.syntax: " + doc.tree.syntax)
//    println("Tree.structure: " + doc.tree.structure)
//    println("Tree.structureLabeled: " + doc.tree.structureLabeled)

    doc.tree.traverse {
      case t@q"..$mods var ..$patsnel: $tpeopt = $expropt" =>
        println(t.symbol.info.get.signature)
    }

    Patch.empty
  }
}

then it will print : Int

https://github.com/DmytroMitin/scalafix-codegen

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
  • I want to instrument external projects at the source code level. So I will have to download the external project, add the semanticdb plugin to it, compile it once and then I can start my instrumentation? Isn't there any easier way to get this information from an existing sbt-based project? – Baradé Jul 22 '19 at 11:25
  • @Baradé At the source code level your program is just a string. There are no types there at all (except manually specified). To get types you should run compiler. Scalameta is not a compiler. – Dmytro Mitin Jul 22 '19 at 12:00
  • @Baradé By the way, [here](https://users.scala-lang.org/t/how-to-instrument-source-code-of-existing-scala-libraries/4717) you said "it seems more like runtime reflection" but in Scala there is also compile-time reflection i.e. macros (def macros https://docs.scala-lang.org/overviews/macros/overview.html and macro annotations https://docs.scala-lang.org/overviews/macros/annotations.html) and compiler plugins (https://docs.scala-lang.org/overviews/plugins/index.html http://dotty.epfl.ch/docs/reference/changed-features/compiler-plugins.html). – Dmytro Mitin Jul 22 '19 at 12:10
  • Is there any way to call your Scalafixdemo class which implements the SemanticRule in a similar way to a scala.meta.Transformer? I would like to read a code file from the disk, generate a SemanticDocument and then transform it to a new one. – Baradé Aug 07 '19 at 19:50
  • @Baradé I guess `doc.tree.collect { case some_tree_pattern => some_patch }.asPatch` is similar to `val transformer = new Transformer { override def apply(tree: Tree): Tree = tree match { case some_tree_pattern => some_other_tree; case tree => super.apply(tree) } }; transformer(tree)`. – Dmytro Mitin Aug 08 '19 at 18:21
  • You can look that `transform/traverse/collect` are implemented via `Transformer/Traverser`. – Dmytro Mitin Aug 08 '19 at 18:24